# Check requisite packages are installed.
packages <- c(
  "plotly", 
  "dplyr",
  "ReacTran",
  "deSolve"
)
for (pkg in packages) {
  library(pkg, character.only = TRUE)
}
package 㤼㸱plotly㤼㸲 was built under R version 4.0.5Loading required package: ggplot2
package 㤼㸱ggplot2㤼㸲 was built under R version 4.0.3
Attaching package: 㤼㸱plotly㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    last_plot

The following object is masked from 㤼㸱package:stats㤼㸲:

    filter

The following object is masked from 㤼㸱package:graphics㤼㸲:

    layout

package 㤼㸱dplyr㤼㸲 was built under R version 4.0.4
Attaching package: 㤼㸱dplyr㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union

package 㤼㸱ReacTran㤼㸲 was built under R version 4.0.5Loading required package: rootSolve
package 㤼㸱rootSolve㤼㸲 was built under R version 4.0.3Loading required package: deSolve
package 㤼㸱deSolve㤼㸲 was built under R version 4.0.3Loading required package: shape
package 㤼㸱shape㤼㸲 was built under R version 4.0.5

Load

Pulling code almost directly from LM1996-NumPoolComScaling-Results-2021-05.Rmd.

dirViking <- c(
  file.path(
    getwd(), "LCAB_LawMorton1996-NumericalPoolCommunityScaling"
  ),
  file.path(
    getwd(), "LCAB_LawMorton1996-NumericalPoolCommunityScaling2"
  )
)
dirVikingResults <- file.path(
  dirViking, c("results-2021-04", "save-2021-05-10") # Latter not 100% yet.
)
resultFormat <- paste0(
  "run-", 
  "%d", # Combination Number, or CombnNum.
  "-", 
  "%s", # Run Seed.
  ".RDS"
)

2021-04 Data

# Copied from LawMorton1996-NumericalPoolCommunityScaling-Calculation.R
set.seed(38427042)

basal <- c(3, 10, 30, 100, 300, 1000)
consumer <- c(3, 10, 30, 100, 300, 1000) * 2
events <- (max(basal) + max(consumer)) * 2
runs <- 100

logBodySize <- c(-2, -1, -1, 1) # Morton and Law 1997 version.
parameters <- c(0.01, 10, 0.5, 0.2, 100, 0.1)

# Need to rerun seedsPrep to get the random number generation right for seedsRun
seedsPrep <- runif(2 * length(basal) * length(consumer)) * 1E8
seedsRun <- runif(runs * length(basal) * length(consumer)) * 1E8
paramFrame <- with(list(
  b = rep(basal, times = length(consumer)),
  c = rep(consumer, each = length(basal)),
  s1 = seedsPrep[1:(length(basal) * length(consumer))],
  s2 = seedsPrep[
    (length(basal) * length(consumer) + 1):(
      2 * length(basal) * length(consumer))
  ],
  sR = seedsRun
), {
  temp <- data.frame(
    CombnNum = 0,
    Basals = b,
    Consumers = c,
    SeedPool = s1,
    SeedMat = s2,
    SeedRuns = "",
    SeedRunsNum = 0,
    EndStates = I(rep(list(""), length(b))),
    EndStatesNum = 0,
    EndStateSizes = I(rep(list(""), length(b))),
    EndStateSizesNum = NA,
    EndStateAssembly = I(rep(list(""), length(b))),
    EndStateAbundance = I(rep(list(""), length(b))),
    Dataset = "2021-04",
    DatasetID = 1,
    stringsAsFactors = FALSE
  )
  for (i in 1:nrow(temp)) {
    seeds <- sR[((i - 1) * runs + 1) : (i * runs)]
    temp$SeedRuns[i] <- toString(seeds) # CSV
    temp$SeedRunsNum[i] <- length(seeds)
  }
  temp$CombnNum <- 1:nrow(temp)
  temp
})
# Note: n + 2 end states. Failure to finish, failure to obtain state, and state.
for (i in 1:nrow(paramFrame)) {
  resultsList <- list(
    "No Run" = 0,
    "No State" = 0
  )
  resultsSize <- list(
    "0" = 0
  )
  resultsAssembly <- list(
    "No Run" = data.frame(),
    "No State" = data.frame()
  )
  seeds <- unlist(strsplit(paramFrame$SeedRuns[i], ', '))
  for (seed in seeds) {
    fileName <- file.path(
      dirVikingResults[paramFrame$DatasetID[i]],
      sprintf(resultFormat, paramFrame$CombnNum[i], seed)
    )
    
    if (file.exists(fileName)) {
      temp <- load(fileName)
      temp <- eval(parse(text = temp)) # Get objects.
      
      if (is.data.frame(temp)) {
        community <- toString(
          temp[[ncol(temp)]][[nrow(temp)]]
        )
        size <- toString(length(temp[[ncol(temp)]][[nrow(temp)]]))
        
        if (community == "") {
          resultsList$`No State` <- resultsList$`No State` + 1
          resultsSize$`0` <- resultsSize$`0` + 1
          
        } else if (community %in% names(resultsList)) {
          resultsList[[community]] <- resultsList[[community]] + 1
          resultsSize[[size]] <- resultsSize[[size]] + 1
          
        } else {
          resultsList[[community]] <- 1
          resultsAssembly[[community]] <- temp
          
          if (size %in% resultsSize) {
            resultsSize[[size]] <- resultsSize[[size]] + 1
          } else {
            resultsSize[[size]] <- 1
          }
        }
      } else {
        resultsList$`No State` <- resultsList$`No State` + 1
        resultsSize$`0` <- resultsSize$`0` + 1
      }
    } else {
      resultsList$`No Run` <- resultsList$`No Run` + 1
      resultsSize$`0` <- resultsSize$`0` + 1
    }
  }
  
  paramFrame$EndStates[[i]] <- resultsList
  paramFrame$EndStatesNum[i] <- length(resultsList) - 2 # ! No State, No Run
  paramFrame$EndStateSizes[[i]] <- resultsSize
  paramFrame$EndStateSizesNum[i] <- length(resultsSize) - 1 # ! 0
  paramFrame$EndStateAssembly[[i]] <- resultsAssembly
}

2021-05 Data

source(
  file.path(getwd(), 
            "LawMorton1996-NumericalPoolCommunityScaling-Settings2.R")
)

oldNrow <- nrow(paramFrame)

paramFrame <- rbind(paramFrame, with(list(
  b = rep(basal, times = length(consumer)),
  c = rep(consumer, each = length(basal)),
  s1 = seedsPrep[1:(length(basal) * length(consumer))],
  s2 = seedsPrep[
    (length(basal) * length(consumer) + 1):(
      2 * length(basal) * length(consumer))
  ],
  sR = seedsRun
), {
  temp <- data.frame(
    CombnNum = 0,
    Basals = b,
    Consumers = c,
    SeedPool = s1,
    SeedMat = s2,
    SeedRuns = "",
    SeedRunsNum = 0,
    EndStates = I(rep(list(""), length(b))),
    EndStatesNum = 0,
    EndStateSizes = I(rep(list(""), length(b))),
    EndStateSizesNum = NA,
    EndStateAssembly = I(rep(list(""), length(b))),
    EndStateAbundance = I(rep(list(""), length(b))),
    Dataset = "2021-05",
    DatasetID = 2,
    stringsAsFactors = FALSE
  )
  for (i in 1:nrow(temp)) {
    seeds <- sR[((i - 1) * runs + 1) : (i * runs)]
    temp$SeedRuns[i] <- toString(seeds) # CSV
    temp$SeedRunsNum[i] <- length(seeds)
  }
  temp$CombnNum <- 1:nrow(temp)
  temp
})
)
# Note: n + 2 end states. Failure to finish, failure to obtain state, and state.
# Modified from above, but with the abundance recorded.
for (i in (oldNrow + 1):nrow(paramFrame)) {
  resultsList <- list(
    "No Run" = 0,
    "No State" = 0
  )
  resultsSize <- list(
    "0" = 0
  )
  resultsAssembly <- list(
    "No Run" = data.frame(),
    "No State" = data.frame()
  )
  resultsAbund <- list(
    "No Run" = "",
    "No State" = ""
  )
  seeds <- unlist(strsplit(paramFrame$SeedRuns[i], ', '))
  for (seed in seeds) {
    fileName <- file.path(
      dirVikingResults[paramFrame$DatasetID[i]],
      sprintf(resultFormat, paramFrame$CombnNum[i], seed)
    )
    
    if (file.exists(fileName)) {
      temp <- load(fileName)
      temp <- eval(parse(text = temp)) # Get objects.
      
      if (is.list(temp) && "Result" %in% names(temp)) {
        
        if (is.data.frame(temp$Result))
          community <- temp$Result$Community[[nrow(temp$Result)]]
        else 
          community <- temp$Result
        
        size <- toString(length(community))
        
        if (community[1] != "") 
          abund <- toString(temp$Abund[community + 1])
        else 
          abund <- ""
        
        community <- toString(community)
        
        if (community == "") {
          resultsList$`No State` <- resultsList$`No State` + 1
          resultsSize$`0` <- resultsSize$`0` + 1
          
        } else if (community %in% names(resultsList)) {
          resultsList[[community]] <- resultsList[[community]] + 1
          resultsSize[[size]] <- resultsSize[[size]] + 1
          
        } else {
          resultsList[[community]] <- 1
          resultsAssembly[[community]] <- temp
          resultsAbund[[community]] <- abund
          
          if (size %in% resultsSize) {
            resultsSize[[size]] <- resultsSize[[size]] + 1
          } else {
            resultsSize[[size]] <- 1
          }
        }
      } else {
        resultsList$`No State` <- resultsList$`No State` + 1
        resultsSize$`0` <- resultsSize$`0` + 1
      }
    } else {
      resultsList$`No Run` <- resultsList$`No Run` + 1
      resultsSize$`0` <- resultsSize$`0` + 1
    }
  }
  
  paramFrame$EndStates[[i]] <- resultsList
  paramFrame$EndStatesNum[i] <- length(resultsList) - 2 # ! No State, No Run
  paramFrame$EndStateSizes[[i]] <- resultsSize
  paramFrame$EndStateSizesNum[i] <- length(resultsSize) - 1 # ! 0
  paramFrame$EndStateAssembly[[i]] <- resultsAssembly
  paramFrame$EndStateAbundance[[i]] <- resultsAbund
}

Test Data

testRowNums <- nrow(paramFrame)
testRowsToAdd <- c(2, 6) # Make sure to put in numerical order!

paramFrame <- with(
  list(
    basal2 = c(5, 10, 15),
    consumer2 = c(20, 40, 60),
    logBodySize = c(-2, -1, -1, 0),
    parameters = c(0.01, 10, 0.5, 0.2, 100, 0.1)
  ),
  {
    set.seed(3680180)
    seedsPrep2 <- runif(2 * length(basal2) * length(consumer2)) * 1E8
    with(list(
      b = rep(basal2, times = length(consumer2)),
      c = rep(consumer2, each = length(basal2)),
      s1 = seedsPrep2[1:(length(basal2) * length(consumer2))],
      s2 = seedsPrep2[
        (length(basal2) * length(consumer2) + 1):(
          2 * length(basal2) * length(consumer2))
      ]
    ), {
      rbind(
        paramFrame,
        data.frame(
          CombnNum = testRowsToAdd,
          Basals = b[testRowsToAdd],
          Consumers = c[testRowsToAdd],
          SeedPool = s1[testRowsToAdd],
          SeedMat = s2[testRowsToAdd],
          SeedRuns = "",
          SeedRunsNum = 0,
          EndStates = I(rep(list(""), length(testRowsToAdd))),
          EndStatesNum = 0,
          EndStateSizes = I(rep(list(""), length(testRowsToAdd))),
          EndStateSizesNum = NA,
          EndStateAssembly = I(rep(list(""), length(testRowsToAdd))),
          EndStateAbundance = I(rep(list(""), length(testRowsToAdd))),
          Dataset = "Test",
          DatasetID = max(paramFrame$DatasetID) + 1,
          stringsAsFactors = FALSE
        )
      )
    }
    )
  }
)

testRowNums <- (testRowNums + 1):nrow(paramFrame)
resultsList <- list(
  list(
    "No Run" = 0,
    "No State" = 0,
    "2, 4, 6, 12, 29" = 1,
    "2, 4, 6, 13, 29" = 1
  ),
  list(
    "No Run" = 0,
    "No State" = 0,
    "8, 10, 12, 14, 15, 16, 39, 43" = 1,
    "8, 12, 14, 15, 16, 38, 39" = 1
  )
)
resultsSize <- list(
  list(
    "0" = 0,
    "5" = 2
  ),
  list(
    "0" = 0,
    "8" = 1,
    "7" = 1
  )
)
resultsAbund <- list(
  list(
    "No Run" = "",
    "No State" = "",
    "2, 4, 6, 12, 29" = "742.88553671712, 80.579233072626, 162.128399850253, 20.2082198699389, 18.8589490510429",
    "2, 4, 6, 13, 29" = "668.664143581837, 119.024146851052, 127.680269383867, 30.657960866033, 13.4844194707944"
  ),
  list(
    "No Run" = "",
    "No State" = "",
    "8, 10, 12, 14, 15, 16, 39, 43" = "20.7665807606491, 32.4461165261454, 80.4033387818895, 817.879722033354, 121.136570782828, 18.0390671088957, 12.3834561177271, 19.9674718543196",
    "8, 12, 14, 15, 16, 38, 39" = "82.592048492812, 138.267379166014, 938.158436379166, 51.8610963745021, 5.03556251837491, 14.1019343145825, 25.9231062711228"
  )
)

for (j in seq_along(testRowNums)) {
  i <- testRowNums[j]
  paramFrame$EndStates[[i]] <- resultsList[[j]]
  paramFrame$EndStatesNum[i] <- length(resultsList[[j]]) - 2 # ! No State, No Run
  paramFrame$EndStateSizes[[i]] <- resultsSize[[j]]
  paramFrame$EndStateSizesNum[i] <- length(resultsSize[[j]]) - 1 # ! 0
  paramFrame$EndStateAbundance[[i]] <- resultsAbund[[j]]
}

Plot

# X, Y, Basal and Consumer.
# Z = Sizes of the Endstates.

plotScalingData <- data.frame(
  CombnNum = rep(paramFrame$CombnNum, paramFrame$EndStatesNum),
  Basals = rep(paramFrame$Basals, paramFrame$EndStatesNum),
  Consumers = rep(paramFrame$Consumers, paramFrame$EndStatesNum),
  Dataset = rep(paramFrame$Dataset, paramFrame$EndStatesNum),
  DatasetID = rep(paramFrame$DatasetID, paramFrame$EndStatesNum)
)

# Communities
comms <- unlist(lapply(paramFrame$EndStates, names))
freqs <- unlist(paramFrame$EndStates)
asmbl <- unlist(paramFrame$EndStateAssembly, recursive = FALSE)
asmbl <- asmbl[comms != "No Run" & comms != "No State"]
freqs <- freqs[comms != "No Run" & comms != "No State"]
comms <- comms[comms != "No Run" & comms != "No State"]

asmbl <- lapply(asmbl, function(d) {
  if (is.null(d)) return(NA)
  if ("Result.Outcome" %in% names(d))
    d %>% dplyr::filter(Result.Outcome != "Type 1 (Failure)" & 
                          Result.Outcome != "Present")
  else
    d$Result %>% dplyr::filter(Outcome != "Type 1 (Failure)" & 
                                 Outcome != "Present")
})

plotScalingData$Communities <- comms
plotScalingData$CommunityFreq <- freqs
plotScalingData$CommunitySeq <- asmbl

# Community Size
temp <- unlist(lapply(strsplit(plotScalingData$Communities, ','), length))
plotScalingData$CommunitySize <- temp

# For usage by the reader.

plotScaling <- plotly::plot_ly(
  plotScalingData,
  x = ~Basals,
  y = ~Consumers,
  z = ~CommunitySize,
  color = ~Dataset,
  colors = c("red", "blue", "black")
)

plotScaling <- plotly::add_markers(plotScaling)

plotScaling <- plotly::layout(
  plotScaling,
  scene = list(
    xaxis = list(type = "log"),
    yaxis = list(type = "log"),
    camera = list(
      eye = list(
        x = -1.25, y = -1.25, z = .05
      )
    )
  )
)

plotScaling

Abundances

# > runif(1) * 1E8
# [1] 82598679
set.seed(82598679)

mats <- list()
poolsall <- list() # name pools used in save data; be careful!

for (i in 1:length(dirViking)) {
  temp <- load(file.path(
    dirViking[i], 
    paste0("LawMorton1996-NumericalPoolCommunityScaling-PoolMats", 
           if (i > 1) i else "", 
           ".RDS")
  ))
  mats[[i]] <- eval(parse(text = temp[1]))
  poolsall[[i]] <- eval(parse(text = temp[2]))
}
pools <- poolsall

# Add in the test datasets.
poolsTemp <- list()
matsTemp <- list()
for (r in testRowNums) {
  testRowRow <- paramFrame[r, ]
  poolsTemp[[testRowRow$CombnNum]] <- with(testRowRow,
    RMTRCode2::LawMorton1996_species(
      Basal = Basals,
      Consumer = Consumers,
      Parameters = c(0.01, 10, 0.5, 0.2, 100, 0.1),
      LogBodySize = c(-2, -1, -1, 0),
      seed = SeedPool
    )
  )
  matsTemp[[testRowRow$CombnNum]] <- with(testRowRow,
    RMTRCode2::LawMorton1996_CommunityMat(
      Pool = poolsTemp[[CombnNum]],
      Parameters = c(0.01, 10, 0.5, 0.2, 100, 0.1),
      seed = SeedMat
    )
  )
}
executing %dopar% sequentially: no parallel backend registered
pools[[i + 1]] <- poolsTemp
mats[[i + 1]] <- matsTemp

oldCandidateData <- load(file.path(getwd(), "candidateDataSoFar.Rdata"))
oldCandidateData <- eval(parse(text = oldCandidateData))
candidateData <- plotScalingData %>% dplyr::group_by(
  CombnNum, Dataset
) %>% dplyr::mutate(
  OtherSteadyStates = dplyr::n() - 1
) %>% dplyr::filter(
  OtherSteadyStates > 0
)
candidateData %>% dplyr::select(-CommunitySeq)
# First, check if it is in the paramFrame.
# Second, check if it is in the saved data from the previous.
# Otherwise, ignore it, we'll figure out what it is and why it is missing later.

candidateData$CommunityAbund <- ""

for (r in 1:nrow(candidateData)) {
  # ID 1:4 are used to identify paramFrame, 5 used to identify abundance
  ID <- candidateData[r, 1:6]
  paramFrameRow <- paramFrame %>% dplyr::filter(
    CombnNum == ID$CombnNum,
    Basals == ID$Basals,
    Consumers == ID$Consumers,
    Dataset == ID$Dataset
  )
  
  if (is.list(paramFrameRow$EndStateAbundance[[1]])) {
    entry <- which(ID$Communities == names(paramFrameRow$EndStateAbundance[[1]]))
    if (length(entry)) {
      candidateData$CommunityAbund[r] <- paramFrameRow$EndStateAbundance[[1]][[entry]]
      next()
    }
  }
  
  if (ID$Dataset == "2021-04") {
    
    oldCandDatRow <- oldCandidateData %>% dplyr::filter(
      CombnNum == ID$CombnNum,
      Basals == ID$Basals,
      Consumers == ID$Consumers,
      Communities == ID$Communities
    )
    
    if (nrow(oldCandDatRow) > 0) {
      if (oldCandDatRow$CommunityAbund != "") {
        candidateData$CommunityAbund[r] <- oldCandDatRow$CommunityAbund
      }
    }
  }
}
for (r in 1:nrow(candidateData)) {
  if (!(candidateData$CommunityAbund[r] == "Failure" |
      candidateData$CommunityAbund[r] == "")) next

  # Random guesses, starting from structured.
  temp <- with(
    candidateData[r, ],
    RMTRCode2::FindSteadyStateFromEstimate(
      Pool = pools[[DatasetID]][[CombnNum]],
      InteractionMatrix = mats[[DatasetID]][[CombnNum]],
      Community = Communities,
      Populations = ifelse(
        pools[[DatasetID]][[CombnNum]]$Type[
          RMTRCode2::CsvRowSplit(Communities)
        ] == "Basal",
        1000, 10)
    )
  )

  if (any(temp) < 1E-4) {
    temp <- "EstimateFailure"
  } else {
    temp <- toString(temp)
  }
  candidateData$CommunityAbund[r] <- temp
}
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2843.06
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2876.33
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2870.65
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2873.23
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 2871.69
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they gocoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logicalcoercing argument of type 'double' to logical
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 6597.38
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they gocoercing argument of type 'double' to logical
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3592.76
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3780.53
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3786.89
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3784.07
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3778.43
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3777.75
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3774.98
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they go
DLSODA-  At current T (=R1), MXSTEP (=I1) steps   
      taken on this call before reaching TOUT     
In above message, I1 = 5000
 
In above message, R1 = 3781.84
 
an excessive amount of work (> maxsteps ) was done, but integration was not successful - increase maxstepsReturning early. Results are accurate, as far as they gocoercing argument of type 'double' to logical
candidateData <- candidateData %>% dplyr::filter(CommunityAbund != "",
                                                 CommunityAbund != "Failure",
                                                 CommunityAbund != "EstimateFailure")
candidateData$CommunityProd <- NA
for (r in 1:nrow(candidateData)) {
  candidateData$CommunityProd[r] <- with(
    candidateData[r, ], 
    RMTRCode2::Productivity(
      Pool = pools[[DatasetID]][[CombnNum]], 
      InteractionMatrix = mats[[DatasetID]][[CombnNum]], 
      Community = Communities, 
      Populations = CommunityAbund
    )
  )
}

Simple Pipe Results

Setup

functionCommunity <- function(
    time, 
    Concentration, 
    parameters, 
    GridThickness,# = Grid,
    Pool,# = preprocessed$redDisPool, 
    Mat,# = preprocessed$redComMat, 
    Area = 1, 
    Diffusion = 1, 
    Verbose = FALSE,
    ExtinctionThreshold = 0, 
    Circular = FALSE,
    nonUniformDiffusion = FALSE
) {
    if(Verbose) print(Concentration)
    
    Concentration[Concentration < ExtinctionThreshold] <- 0
    
    # Columns are per species pipes,
    # Rows are the same point on a pipe but for each species.
    pipeMatrix <- matrix(data = Concentration, ncol = nrow(Pool))
    # Note that matrix automatically fills downward!
    
    # Idea with circular: attach duplicates of first, last evaluation points.
    # Then evaluate as normal. Before finishing, remove the duplicate entries.
    # Order of additions matters.
    if (Circular > 0) {
      pipeMatrix <- rbind(pipeMatrix[-Circular:-1 + 1 +nrow(pipeMatrix), ],
                          pipeMatrix,
                          pipeMatrix[1:Circular, ])
      
      if ("dx" %in% names(GridThickness)) {
        GridThickness <- GridThickness$dx
      }
      GridThickness <- c(GridThickness[-Circular:-1 + 1 + length(GridThickness)],
                         GridThickness,
                         GridThickness[1:Circular])
    }
    
    if(Verbose) print(pipeMatrix)
    
    pipeList <- lapply(1:ncol(pipeMatrix), 
                       FUN = function(i, m) {m[, i]}, 
                       m = pipeMatrix)
    
    # Take the species columns to be in the same order as the pool.
    pipeListTran <- lapply(
      seq_along(pipeList), 
      FUN = function(i, C, A, D, G, M) {
        ReacTran::tran.1D(
          C = C[[i]], A = A,
          D = D * M ^ (
            if (nonUniformDiffusion == "positive") 0.62
            else if (nonUniformDiffusion == "negative") -0.62
            else 0
          ),
          dx = G
        )$dC
      }, 
      C = pipeList, A = Area, D = Diffusion, G = GridThickness, M = Pool$Size)
    
    # Diffusion
    pipeMatrixTran <- matrix(
      unlist(pipeListTran),
      nrow = nrow(pipeMatrix),
      ncol = ncol(pipeMatrix)
    )
    
    if(Verbose) print(pipeMatrixTran)
    
    # Point-wise community dynamics
    pipeMatrixComm <- t(apply(
      pipeMatrix, MARGIN = 1, FUN = function(C, t, P, M) {
      unlist(RMTRCode2::GeneralisedLotkaVolterra(t = t, y = C, 
                                      parms = list(
                                        a = M,
                                        r = P$ReproductionRate
                                      )))
    }, t = time, P = Pool, M = Mat))
    
    if(Verbose) print(pipeMatrixComm)
    
    # Order of deletions does not matter.
    if(Circular) pipeMatrixTran <- pipeMatrixTran[
      c(-1:-Circular, -1:-Circular + 1 + nrow(pipeMatrixTran)), ] 
    if(Circular) pipeMatrixComm <- pipeMatrixComm[
      c(-1:-Circular, -1:-Circular + 1 + nrow(pipeMatrixComm)), ] 
    
    list(pipeMatrixTran + pipeMatrixComm)
  }

pipeFUN <- function(i, dat, pool, mat, 
                    GridL = 1, GridCells = 100,
                    Time = seq(0, 10000, by = 2),
                    nonUniformDiffusion = FALSE) {
  temp <- dat[i, ]
  
  preprocessed <- RMTRCode2::IslandPreprocess(
    Pool = pool, InteractionMatrix = mat,
    Communities = c(
      list(temp$Communities[1]),
      list(temp$Communities[2])
    ),
    Populations = c(
      list(temp$CommunityAbund[1]),
      list(temp$CommunityAbund[2])
    ),
    DispersalPool = 0,
    DispersalIsland = matrix(0, nrow = 2, ncol = 2),
    Tolerance = 0, 
    Verbose = FALSE
  )
  
  # Setup the de system.
  Grid <- ReacTran::setup.grid.1D(L = GridL, N = GridCells)
  
  initialCondition <- with(preprocessed, rbind(
    # Community 1
    matrix(abundance_init[1:length(redCom)], ncol = length(redCom)),
    # Space
    matrix(0, nrow = GridCells - 2, ncol = length(redCom)),
    # Community 2
    matrix(abundance_init[length(redCom) + 1:length(redCom)], ncol = length(redCom))
  ))
  
  # https://www.jstor.org/stable/3071786 figure 1
  # suggests size-dispersal should be 10^0.62
  deSolve::ode.1D(
    y = initialCondition, 
    times = Time,
    func = functionCommunity,
    parms = preprocessed$parameters,
    D = 0.001,
    nspec = length(preprocessed$redCom),
    Verbose = FALSE,
    GridThickness = Grid,
    Pool = preprocessed$redPool, 
    Mat = preprocessed$redComMat, 
    nonUniformDiffusion = nonUniformDiffusion
  )
}
# For each group-dataset,
# For each pair,
# Run Pipe Dynamics,
# Save the result with its pairing
candidateData$TotalID <- paste(candidateData$CombnNum, candidateData$DatasetID)

pipeInteractions <- list()
pipeInteractionsFull <- list()
pipeInteractionsP <- list()
pipeInteractionsPFull <- list()
pipeInteractionsN <- list()
pipeInteractionsNFull <- list()
pipeInteractionsTemp <- list()
pipeInteractionsTempFull <- list()

for (grp in unique(candidateData$TotalID)) {
  candidateDataSubset <- candidateData %>% dplyr::filter(TotalID == grp)
  
  if (nrow(candidateDataSubset) == 1) next()
  
  for (nonUniformDiffusion in c(FALSE, "positive", "negative")) {
    pairingResults <- combn(
      nrow(candidateDataSubset), 2, 
      pipeFUN,
      dat = candidateDataSubset, 
      pool = pools[[
        candidateDataSubset$DatasetID[1]
      ]][[candidateDataSubset$CombnNum[1]]],
      mat = mats[[
        candidateDataSubset$DatasetID[1]
      ]][[candidateDataSubset$CombnNum[1]]],
      simplify = FALSE,
      nonUniformDiffusion = nonUniformDiffusion
    )
    
    pairingResultsAbunds <- lapply(
      pairingResults, function(mat, cells = 100) {
        # Final outcomes, without time.
        mat <- mat[nrow(mat), -1]
        retVal <- list()
        species <- length(mat) / cells
        # The results are copies of the pipe by species
        # We want the end points.
        for (i in c(1, cells)) {
          retVal[[toString(i)]] <- mat[i + cells * ((1:species) - 1)]
        }
        retVal
      }
    )
  
    pipeInteractionsTempFull[[
      toString(nonUniformDiffusion)]][[grp]] <- pairingResults
    pipeInteractionsTemp[[
      toString(nonUniformDiffusion)]][[grp]] <- pairingResultsAbunds
  }
}

pipeInteractionsFull <- pipeInteractionsTempFull[["FALSE"]]
pipeInteractions <- pipeInteractionsTemp[["FALSE"]]
pipeInteractionsPFull <- pipeInteractionsTempFull[["positive"]]
pipeInteractionsP <- pipeInteractionsTemp[["positive"]]
pipeInteractionsNFull <- pipeInteractionsTempFull[["negative"]]
pipeInteractionsN <- pipeInteractionsTemp[["negative"]]

Results Size-Negative

# Format of table should be:
# ID, Community 1, Community 2, Outcomes 1-2, Outcomes 1-0-2
# For outcomes, species presence will be used.

communities <- NULL
totalCommunities <- NULL
for (grp in unique(candidateData$TotalID)) {
  candidateDataSubset <- candidateData %>% dplyr::filter(TotalID == grp)
  
  if (nrow(candidateDataSubset) > 1) {
    newCommunities <- combn(
      candidateDataSubset$Communities, 2, 
    )
    communities <- c(communities, newCommunities)
    totalCommunities <- c( # Labelling is wrong here. Need tC from pairs of nC.
      totalCommunities,
      list(apply(newCommunities, 2, function(coms) {
        toString(sort(unique(RMTRCode2::CsvRowSplit(coms))))
      }))
    )
  }
}

minThresh <- lapply(candidateData$CommunityAbund, function(x) min(RMTRCode2::CsvRowSplit(x)) * 0.1) %>% unlist %>% min

pipeInteractionsNWhich <- unlist(lapply(
  seq_along(pipeInteractionsN), function(i, byID, communities) {
    lapply(byID[[i]], function(system, comms) {
      lapply(system, function(pipeEnd, comms) {
        toString(RMTRCode2::CsvRowSplit(comms)[
          which(pipeEnd > minThresh)
        ])
      }, comms = comms)
    }, comms = communities[[i]])
  }, byID = pipeInteractionsN, communities = totalCommunities
))

pipeInteractionNResults <- data.frame(
  DatasetID = rep(names(pipeInteractionsN), 
                  unlist(lapply(pipeInteractionsN, length))),
  Community1 = communities[seq(from = 1, to = length(communities), by = 2)],
  Community2 = communities[seq(from = 2, to = length(communities), by = 2)],
  Outcome_Pipe1 = pipeInteractionsNWhich[
    seq(from = 1, to = length(pipeInteractionsNWhich), by = 2)],
  Outcome_Pipe2 = pipeInteractionsNWhich[
    seq(from = 1, to = length(pipeInteractionsNWhich), by = 2)]
)

pipeInteractionNResults
pipeInteractionNResults %>% dplyr::mutate(
  C1Invaded = Community1 != Outcome_Pipe1,
  C2Invaded = Community2 != Outcome_Pipe2,
  Stalemate = !C1Invaded & !C2Invaded,
  Hybrid = C1Invaded & C2Invaded,
) %>% dplyr::select(-dplyr::starts_with("Outcome"))

Results Size-Neutral

pipeInteractionsWhich <- unlist(lapply(
  seq_along(pipeInteractions), function(i, byID, communities) {
    lapply(byID[[i]], function(system, comms) {
      lapply(system, function(pipeEnd, comms) {
        toString(RMTRCode2::CsvRowSplit(comms)[
          which(pipeEnd > minThresh)
        ])
      }, comms = comms)
    }, comms = communities[[i]])
  }, byID = pipeInteractions, communities = totalCommunities
))

pipeInteractionResults <- data.frame(
  DatasetID = rep(names(pipeInteractions), 
                  unlist(lapply(pipeInteractions, length))),
  Community1 = communities[seq(from = 1, to = length(communities), by = 2)],
  Community2 = communities[seq(from = 2, to = length(communities), by = 2)],
  Outcome_Pipe1 = pipeInteractionsWhich[
    seq(from = 1, to = length(pipeInteractionsWhich), by = 2)],
  Outcome_Pipe2 = pipeInteractionsWhich[
    seq(from = 1, to = length(pipeInteractionsWhich), by = 2)]
)

pipeInteractionResults
pipeInteractionResults %>% dplyr::mutate(
  C1Invaded = Community1 != Outcome_Pipe1,
  C2Invaded = Community2 != Outcome_Pipe2,
  Stalemate = !C1Invaded & !C2Invaded,
  Hybrid = C1Invaded & C2Invaded,
) %>% dplyr::select(-dplyr::starts_with("Outcome"))

Results Size-Positive

pipeInteractionsPWhich <- unlist(lapply(
  seq_along(pipeInteractionsP), function(i, byID, communities) {
    lapply(byID[[i]], function(system, comms) {
      lapply(system, function(pipeEnd, comms) {
        toString(RMTRCode2::CsvRowSplit(comms)[
          which(pipeEnd > minThresh)
        ])
      }, comms = comms)
    }, comms = communities[[i]])
  }, byID = pipeInteractionsP, communities = totalCommunities
))

pipeInteractionPResults <- data.frame(
  DatasetID = rep(names(pipeInteractionsP), 
                  unlist(lapply(pipeInteractionsP, length))),
  Community1 = communities[seq(from = 1, to = length(communities), by = 2)],
  Community2 = communities[seq(from = 2, to = length(communities), by = 2)],
  Outcome_Pipe1 = pipeInteractionsPWhich[
    seq(from = 1, to = length(pipeInteractionsPWhich), by = 2)],
  Outcome_Pipe2 = pipeInteractionsPWhich[
    seq(from = 1, to = length(pipeInteractionsPWhich), by = 2)]
)

pipeInteractionPResults
pipeInteractionPResults %>% dplyr::mutate(
  C1Invaded = Community1 != Outcome_Pipe1,
  C2Invaded = Community2 != Outcome_Pipe2,
  Stalemate = !C1Invaded & !C2Invaded,
  Hybrid = C1Invaded & C2Invaded,
) %>% dplyr::select(-dplyr::starts_with("Outcome"))

Save workspace

save(
  candidateData,
  pipeInteractions,
  pipeInteractionsWhich,
  pipeInteractionsP,
  pipeInteractionsPWhich,
  pipeInteractionsN,
  pipeInteractionsNWhich,
  mats,
  paramFrame,
  plotScalingData,
  pools,
  file = paste0("LM1996-NumPoolCom-QDatDif",
                "-2021-07.RData")
)

save(
  pipeInteractionsNFull,
  pipeInteractionsFull,
  pipeInteractionsPFull,
  file = paste0("LM1996-NumPoolCom-QDatDif",
                "FULL-2021-07.RData")
)
LS0tDQp0aXRsZTogIkFuc3dlcmluZyBRdWVzdGlvbnM6IEdhdGhlciBEYXRhOiBEaWZmdXNpb24sIDIwMjEtMDciDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NCg0KYGBge3IgbGlic30NCiMgQ2hlY2sgcmVxdWlzaXRlIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQuDQpwYWNrYWdlcyA8LSBjKA0KICAicGxvdGx5IiwgDQogICJkcGx5ciIsDQogICJSZWFjVHJhbiIsDQogICJkZVNvbHZlIg0KKQ0KZm9yIChwa2cgaW4gcGFja2FnZXMpIHsNCiAgbGlicmFyeShwa2csIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCn0NCmBgYA0KDQojIExvYWQNClB1bGxpbmcgY29kZSBhbG1vc3QgZGlyZWN0bHkgZnJvbSBgTE0xOTk2LU51bVBvb2xDb21TY2FsaW5nLVJlc3VsdHMtMjAyMS0wNS5SbWRgLg0KYGBge3IgZGlyc30NCmRpclZpa2luZyA8LSBjKA0KICBmaWxlLnBhdGgoDQogICAgZ2V0d2QoKSwgIkxDQUJfTGF3TW9ydG9uMTk5Ni1OdW1lcmljYWxQb29sQ29tbXVuaXR5U2NhbGluZyINCiAgKSwNCiAgZmlsZS5wYXRoKA0KICAgIGdldHdkKCksICJMQ0FCX0xhd01vcnRvbjE5OTYtTnVtZXJpY2FsUG9vbENvbW11bml0eVNjYWxpbmcyIg0KICApDQopDQpkaXJWaWtpbmdSZXN1bHRzIDwtIGZpbGUucGF0aCgNCiAgZGlyVmlraW5nLCBjKCJyZXN1bHRzLTIwMjEtMDQiLCAic2F2ZS0yMDIxLTA1LTEwIikgIyBMYXR0ZXIgbm90IDEwMCUgeWV0Lg0KKQ0KcmVzdWx0Rm9ybWF0IDwtIHBhc3RlMCgNCiAgInJ1bi0iLCANCiAgIiVkIiwgIyBDb21iaW5hdGlvbiBOdW1iZXIsIG9yIENvbWJuTnVtLg0KICAiLSIsIA0KICAiJXMiLCAjIFJ1biBTZWVkLg0KICAiLlJEUyINCikNCmBgYA0KDQojIyAyMDIxLTA0IERhdGENCmBgYHtyIHBhcmFtc30NCiMgQ29waWVkIGZyb20gTGF3TW9ydG9uMTk5Ni1OdW1lcmljYWxQb29sQ29tbXVuaXR5U2NhbGluZy1DYWxjdWxhdGlvbi5SDQpzZXQuc2VlZCgzODQyNzA0MikNCg0KYmFzYWwgPC0gYygzLCAxMCwgMzAsIDEwMCwgMzAwLCAxMDAwKQ0KY29uc3VtZXIgPC0gYygzLCAxMCwgMzAsIDEwMCwgMzAwLCAxMDAwKSAqIDINCmV2ZW50cyA8LSAobWF4KGJhc2FsKSArIG1heChjb25zdW1lcikpICogMg0KcnVucyA8LSAxMDANCg0KbG9nQm9keVNpemUgPC0gYygtMiwgLTEsIC0xLCAxKSAjIE1vcnRvbiBhbmQgTGF3IDE5OTcgdmVyc2lvbi4NCnBhcmFtZXRlcnMgPC0gYygwLjAxLCAxMCwgMC41LCAwLjIsIDEwMCwgMC4xKQ0KDQojIE5lZWQgdG8gcmVydW4gc2VlZHNQcmVwIHRvIGdldCB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0aW9uIHJpZ2h0IGZvciBzZWVkc1J1bg0Kc2VlZHNQcmVwIDwtIHJ1bmlmKDIgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkgKiAxRTgNCnNlZWRzUnVuIDwtIHJ1bmlmKHJ1bnMgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkgKiAxRTgNCmBgYA0KDQpgYGB7ciBvcmdhbmlzZVBhcmFtc30NCnBhcmFtRnJhbWUgPC0gd2l0aChsaXN0KA0KICBiID0gcmVwKGJhc2FsLCB0aW1lcyA9IGxlbmd0aChjb25zdW1lcikpLA0KICBjID0gcmVwKGNvbnN1bWVyLCBlYWNoID0gbGVuZ3RoKGJhc2FsKSksDQogIHMxID0gc2VlZHNQcmVwWzE6KGxlbmd0aChiYXNhbCkgKiBsZW5ndGgoY29uc3VtZXIpKV0sDQogIHMyID0gc2VlZHNQcmVwWw0KICAgIChsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSArIDEpOigNCiAgICAgIDIgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkNCiAgXSwNCiAgc1IgPSBzZWVkc1J1bg0KKSwgew0KICB0ZW1wIDwtIGRhdGEuZnJhbWUoDQogICAgQ29tYm5OdW0gPSAwLA0KICAgIEJhc2FscyA9IGIsDQogICAgQ29uc3VtZXJzID0gYywNCiAgICBTZWVkUG9vbCA9IHMxLA0KICAgIFNlZWRNYXQgPSBzMiwNCiAgICBTZWVkUnVucyA9ICIiLA0KICAgIFNlZWRSdW5zTnVtID0gMCwNCiAgICBFbmRTdGF0ZXMgPSBJKHJlcChsaXN0KCIiKSwgbGVuZ3RoKGIpKSksDQogICAgRW5kU3RhdGVzTnVtID0gMCwNCiAgICBFbmRTdGF0ZVNpemVzID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlU2l6ZXNOdW0gPSBOQSwNCiAgICBFbmRTdGF0ZUFzc2VtYmx5ID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlQWJ1bmRhbmNlID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIERhdGFzZXQgPSAiMjAyMS0wNCIsDQogICAgRGF0YXNldElEID0gMSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KICBmb3IgKGkgaW4gMTpucm93KHRlbXApKSB7DQogICAgc2VlZHMgPC0gc1JbKChpIC0gMSkgKiBydW5zICsgMSkgOiAoaSAqIHJ1bnMpXQ0KICAgIHRlbXAkU2VlZFJ1bnNbaV0gPC0gdG9TdHJpbmcoc2VlZHMpICMgQ1NWDQogICAgdGVtcCRTZWVkUnVuc051bVtpXSA8LSBsZW5ndGgoc2VlZHMpDQogIH0NCiAgdGVtcCRDb21ibk51bSA8LSAxOm5yb3codGVtcCkNCiAgdGVtcA0KfSkNCmBgYA0KDQpgYGB7ciBsb2FkUmVzdWx0c30NCiMgTm90ZTogbiArIDIgZW5kIHN0YXRlcy4gRmFpbHVyZSB0byBmaW5pc2gsIGZhaWx1cmUgdG8gb2J0YWluIHN0YXRlLCBhbmQgc3RhdGUuDQpmb3IgKGkgaW4gMTpucm93KHBhcmFtRnJhbWUpKSB7DQogIHJlc3VsdHNMaXN0IDwtIGxpc3QoDQogICAgIk5vIFJ1biIgPSAwLA0KICAgICJObyBTdGF0ZSIgPSAwDQogICkNCiAgcmVzdWx0c1NpemUgPC0gbGlzdCgNCiAgICAiMCIgPSAwDQogICkNCiAgcmVzdWx0c0Fzc2VtYmx5IDwtIGxpc3QoDQogICAgIk5vIFJ1biIgPSBkYXRhLmZyYW1lKCksDQogICAgIk5vIFN0YXRlIiA9IGRhdGEuZnJhbWUoKQ0KICApDQogIHNlZWRzIDwtIHVubGlzdChzdHJzcGxpdChwYXJhbUZyYW1lJFNlZWRSdW5zW2ldLCAnLCAnKSkNCiAgZm9yIChzZWVkIGluIHNlZWRzKSB7DQogICAgZmlsZU5hbWUgPC0gZmlsZS5wYXRoKA0KICAgICAgZGlyVmlraW5nUmVzdWx0c1twYXJhbUZyYW1lJERhdGFzZXRJRFtpXV0sDQogICAgICBzcHJpbnRmKHJlc3VsdEZvcm1hdCwgcGFyYW1GcmFtZSRDb21ibk51bVtpXSwgc2VlZCkNCiAgICApDQogICAgDQogICAgaWYgKGZpbGUuZXhpc3RzKGZpbGVOYW1lKSkgew0KICAgICAgdGVtcCA8LSBsb2FkKGZpbGVOYW1lKQ0KICAgICAgdGVtcCA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wKSkgIyBHZXQgb2JqZWN0cy4NCiAgICAgIA0KICAgICAgaWYgKGlzLmRhdGEuZnJhbWUodGVtcCkpIHsNCiAgICAgICAgY29tbXVuaXR5IDwtIHRvU3RyaW5nKA0KICAgICAgICAgIHRlbXBbW25jb2wodGVtcCldXVtbbnJvdyh0ZW1wKV1dDQogICAgICAgICkNCiAgICAgICAgc2l6ZSA8LSB0b1N0cmluZyhsZW5ndGgodGVtcFtbbmNvbCh0ZW1wKV1dW1tucm93KHRlbXApXV0pKQ0KICAgICAgICANCiAgICAgICAgaWYgKGNvbW11bml0eSA9PSAiIikgew0KICAgICAgICAgIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgPC0gcmVzdWx0c0xpc3QkYE5vIFN0YXRlYCArIDENCiAgICAgICAgICByZXN1bHRzU2l6ZSRgMGAgPC0gcmVzdWx0c1NpemUkYDBgICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2UgaWYgKGNvbW11bml0eSAlaW4lIG5hbWVzKHJlc3VsdHNMaXN0KSkgew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSByZXN1bHRzTGlzdFtbY29tbXVuaXR5XV0gKyAxDQogICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSByZXN1bHRzU2l6ZVtbc2l6ZV1dICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSAxDQogICAgICAgICAgcmVzdWx0c0Fzc2VtYmx5W1tjb21tdW5pdHldXSA8LSB0ZW1wDQogICAgICAgICAgDQogICAgICAgICAgaWYgKHNpemUgJWluJSByZXN1bHRzU2l6ZSkgew0KICAgICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSByZXN1bHRzU2l6ZVtbc2l6ZV1dICsgMQ0KICAgICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgICByZXN1bHRzU2l6ZVtbc2l6ZV1dIDwtIDENCiAgICAgICAgICB9DQogICAgICAgIH0NCiAgICAgIH0gZWxzZSB7DQogICAgICAgIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgPC0gcmVzdWx0c0xpc3QkYE5vIFN0YXRlYCArIDENCiAgICAgICAgcmVzdWx0c1NpemUkYDBgIDwtIHJlc3VsdHNTaXplJGAwYCArIDENCiAgICAgIH0NCiAgICB9IGVsc2Ugew0KICAgICAgcmVzdWx0c0xpc3QkYE5vIFJ1bmAgPC0gcmVzdWx0c0xpc3QkYE5vIFJ1bmAgKyAxDQogICAgICByZXN1bHRzU2l6ZSRgMGAgPC0gcmVzdWx0c1NpemUkYDBgICsgMQ0KICAgIH0NCiAgfQ0KICANCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZXNbW2ldXSA8LSByZXN1bHRzTGlzdA0KICBwYXJhbUZyYW1lJEVuZFN0YXRlc051bVtpXSA8LSBsZW5ndGgocmVzdWx0c0xpc3QpIC0gMiAjICEgTm8gU3RhdGUsIE5vIFJ1bg0KICBwYXJhbUZyYW1lJEVuZFN0YXRlU2l6ZXNbW2ldXSA8LSByZXN1bHRzU2l6ZQ0KICBwYXJhbUZyYW1lJEVuZFN0YXRlU2l6ZXNOdW1baV0gPC0gbGVuZ3RoKHJlc3VsdHNTaXplKSAtIDEgIyAhIDANCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZUFzc2VtYmx5W1tpXV0gPC0gcmVzdWx0c0Fzc2VtYmx5DQp9DQpgYGANCg0KIyMgMjAyMS0wNSBEYXRhDQpgYGB7ciBvcmdhbmlzZVBhcmFtczJ9DQpzb3VyY2UoDQogIGZpbGUucGF0aChnZXR3ZCgpLCANCiAgICAgICAgICAgICJMYXdNb3J0b24xOTk2LU51bWVyaWNhbFBvb2xDb21tdW5pdHlTY2FsaW5nLVNldHRpbmdzMi5SIikNCikNCg0Kb2xkTnJvdyA8LSBucm93KHBhcmFtRnJhbWUpDQoNCnBhcmFtRnJhbWUgPC0gcmJpbmQocGFyYW1GcmFtZSwgd2l0aChsaXN0KA0KICBiID0gcmVwKGJhc2FsLCB0aW1lcyA9IGxlbmd0aChjb25zdW1lcikpLA0KICBjID0gcmVwKGNvbnN1bWVyLCBlYWNoID0gbGVuZ3RoKGJhc2FsKSksDQogIHMxID0gc2VlZHNQcmVwWzE6KGxlbmd0aChiYXNhbCkgKiBsZW5ndGgoY29uc3VtZXIpKV0sDQogIHMyID0gc2VlZHNQcmVwWw0KICAgIChsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSArIDEpOigNCiAgICAgIDIgKiBsZW5ndGgoYmFzYWwpICogbGVuZ3RoKGNvbnN1bWVyKSkNCiAgXSwNCiAgc1IgPSBzZWVkc1J1bg0KKSwgew0KICB0ZW1wIDwtIGRhdGEuZnJhbWUoDQogICAgQ29tYm5OdW0gPSAwLA0KICAgIEJhc2FscyA9IGIsDQogICAgQ29uc3VtZXJzID0gYywNCiAgICBTZWVkUG9vbCA9IHMxLA0KICAgIFNlZWRNYXQgPSBzMiwNCiAgICBTZWVkUnVucyA9ICIiLA0KICAgIFNlZWRSdW5zTnVtID0gMCwNCiAgICBFbmRTdGF0ZXMgPSBJKHJlcChsaXN0KCIiKSwgbGVuZ3RoKGIpKSksDQogICAgRW5kU3RhdGVzTnVtID0gMCwNCiAgICBFbmRTdGF0ZVNpemVzID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlU2l6ZXNOdW0gPSBOQSwNCiAgICBFbmRTdGF0ZUFzc2VtYmx5ID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIEVuZFN0YXRlQWJ1bmRhbmNlID0gSShyZXAobGlzdCgiIiksIGxlbmd0aChiKSkpLA0KICAgIERhdGFzZXQgPSAiMjAyMS0wNSIsDQogICAgRGF0YXNldElEID0gMiwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KICBmb3IgKGkgaW4gMTpucm93KHRlbXApKSB7DQogICAgc2VlZHMgPC0gc1JbKChpIC0gMSkgKiBydW5zICsgMSkgOiAoaSAqIHJ1bnMpXQ0KICAgIHRlbXAkU2VlZFJ1bnNbaV0gPC0gdG9TdHJpbmcoc2VlZHMpICMgQ1NWDQogICAgdGVtcCRTZWVkUnVuc051bVtpXSA8LSBsZW5ndGgoc2VlZHMpDQogIH0NCiAgdGVtcCRDb21ibk51bSA8LSAxOm5yb3codGVtcCkNCiAgdGVtcA0KfSkNCikNCmBgYA0KDQpgYGB7ciBsb2FkUmVzdWx0czJ9DQojIE5vdGU6IG4gKyAyIGVuZCBzdGF0ZXMuIEZhaWx1cmUgdG8gZmluaXNoLCBmYWlsdXJlIHRvIG9idGFpbiBzdGF0ZSwgYW5kIHN0YXRlLg0KIyBNb2RpZmllZCBmcm9tIGFib3ZlLCBidXQgd2l0aCB0aGUgYWJ1bmRhbmNlIHJlY29yZGVkLg0KZm9yIChpIGluIChvbGROcm93ICsgMSk6bnJvdyhwYXJhbUZyYW1lKSkgew0KICByZXN1bHRzTGlzdCA8LSBsaXN0KA0KICAgICJObyBSdW4iID0gMCwNCiAgICAiTm8gU3RhdGUiID0gMA0KICApDQogIHJlc3VsdHNTaXplIDwtIGxpc3QoDQogICAgIjAiID0gMA0KICApDQogIHJlc3VsdHNBc3NlbWJseSA8LSBsaXN0KA0KICAgICJObyBSdW4iID0gZGF0YS5mcmFtZSgpLA0KICAgICJObyBTdGF0ZSIgPSBkYXRhLmZyYW1lKCkNCiAgKQ0KICByZXN1bHRzQWJ1bmQgPC0gbGlzdCgNCiAgICAiTm8gUnVuIiA9ICIiLA0KICAgICJObyBTdGF0ZSIgPSAiIg0KICApDQogIHNlZWRzIDwtIHVubGlzdChzdHJzcGxpdChwYXJhbUZyYW1lJFNlZWRSdW5zW2ldLCAnLCAnKSkNCiAgZm9yIChzZWVkIGluIHNlZWRzKSB7DQogICAgZmlsZU5hbWUgPC0gZmlsZS5wYXRoKA0KICAgICAgZGlyVmlraW5nUmVzdWx0c1twYXJhbUZyYW1lJERhdGFzZXRJRFtpXV0sDQogICAgICBzcHJpbnRmKHJlc3VsdEZvcm1hdCwgcGFyYW1GcmFtZSRDb21ibk51bVtpXSwgc2VlZCkNCiAgICApDQogICAgDQogICAgaWYgKGZpbGUuZXhpc3RzKGZpbGVOYW1lKSkgew0KICAgICAgdGVtcCA8LSBsb2FkKGZpbGVOYW1lKQ0KICAgICAgdGVtcCA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wKSkgIyBHZXQgb2JqZWN0cy4NCiAgICAgIA0KICAgICAgaWYgKGlzLmxpc3QodGVtcCkgJiYgIlJlc3VsdCIgJWluJSBuYW1lcyh0ZW1wKSkgew0KICAgICAgICANCiAgICAgICAgaWYgKGlzLmRhdGEuZnJhbWUodGVtcCRSZXN1bHQpKQ0KICAgICAgICAgIGNvbW11bml0eSA8LSB0ZW1wJFJlc3VsdCRDb21tdW5pdHlbW25yb3codGVtcCRSZXN1bHQpXV0NCiAgICAgICAgZWxzZSANCiAgICAgICAgICBjb21tdW5pdHkgPC0gdGVtcCRSZXN1bHQNCiAgICAgICAgDQogICAgICAgIHNpemUgPC0gdG9TdHJpbmcobGVuZ3RoKGNvbW11bml0eSkpDQogICAgICAgIA0KICAgICAgICBpZiAoY29tbXVuaXR5WzFdICE9ICIiKSANCiAgICAgICAgICBhYnVuZCA8LSB0b1N0cmluZyh0ZW1wJEFidW5kW2NvbW11bml0eSArIDFdKQ0KICAgICAgICBlbHNlIA0KICAgICAgICAgIGFidW5kIDwtICIiDQogICAgICAgIA0KICAgICAgICBjb21tdW5pdHkgPC0gdG9TdHJpbmcoY29tbXVuaXR5KQ0KICAgICAgICANCiAgICAgICAgaWYgKGNvbW11bml0eSA9PSAiIikgew0KICAgICAgICAgIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgPC0gcmVzdWx0c0xpc3QkYE5vIFN0YXRlYCArIDENCiAgICAgICAgICByZXN1bHRzU2l6ZSRgMGAgPC0gcmVzdWx0c1NpemUkYDBgICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2UgaWYgKGNvbW11bml0eSAlaW4lIG5hbWVzKHJlc3VsdHNMaXN0KSkgew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSByZXN1bHRzTGlzdFtbY29tbXVuaXR5XV0gKyAxDQogICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSByZXN1bHRzU2l6ZVtbc2l6ZV1dICsgMQ0KICAgICAgICAgIA0KICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgIHJlc3VsdHNMaXN0W1tjb21tdW5pdHldXSA8LSAxDQogICAgICAgICAgcmVzdWx0c0Fzc2VtYmx5W1tjb21tdW5pdHldXSA8LSB0ZW1wDQogICAgICAgICAgcmVzdWx0c0FidW5kW1tjb21tdW5pdHldXSA8LSBhYnVuZA0KICAgICAgICAgIA0KICAgICAgICAgIGlmIChzaXplICVpbiUgcmVzdWx0c1NpemUpIHsNCiAgICAgICAgICAgIHJlc3VsdHNTaXplW1tzaXplXV0gPC0gcmVzdWx0c1NpemVbW3NpemVdXSArIDENCiAgICAgICAgICB9IGVsc2Ugew0KICAgICAgICAgICAgcmVzdWx0c1NpemVbW3NpemVdXSA8LSAxDQogICAgICAgICAgfQ0KICAgICAgICB9DQogICAgICB9IGVsc2Ugew0KICAgICAgICByZXN1bHRzTGlzdCRgTm8gU3RhdGVgIDwtIHJlc3VsdHNMaXN0JGBObyBTdGF0ZWAgKyAxDQogICAgICAgIHJlc3VsdHNTaXplJGAwYCA8LSByZXN1bHRzU2l6ZSRgMGAgKyAxDQogICAgICB9DQogICAgfSBlbHNlIHsNCiAgICAgIHJlc3VsdHNMaXN0JGBObyBSdW5gIDwtIHJlc3VsdHNMaXN0JGBObyBSdW5gICsgMQ0KICAgICAgcmVzdWx0c1NpemUkYDBgIDwtIHJlc3VsdHNTaXplJGAwYCArIDENCiAgICB9DQogIH0NCiAgDQogIHBhcmFtRnJhbWUkRW5kU3RhdGVzW1tpXV0gPC0gcmVzdWx0c0xpc3QNCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZXNOdW1baV0gPC0gbGVuZ3RoKHJlc3VsdHNMaXN0KSAtIDIgIyAhIE5vIFN0YXRlLCBObyBSdW4NCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZVNpemVzW1tpXV0gPC0gcmVzdWx0c1NpemUNCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZVNpemVzTnVtW2ldIDwtIGxlbmd0aChyZXN1bHRzU2l6ZSkgLSAxICMgISAwDQogIHBhcmFtRnJhbWUkRW5kU3RhdGVBc3NlbWJseVtbaV1dIDwtIHJlc3VsdHNBc3NlbWJseQ0KICBwYXJhbUZyYW1lJEVuZFN0YXRlQWJ1bmRhbmNlW1tpXV0gPC0gcmVzdWx0c0FidW5kDQp9DQpgYGANCg0KIyMgVGVzdCBEYXRhDQoNCmBgYHtyIGFkZFRlc3R9DQp0ZXN0Um93TnVtcyA8LSBucm93KHBhcmFtRnJhbWUpDQp0ZXN0Um93c1RvQWRkIDwtIGMoMiwgNikgIyBNYWtlIHN1cmUgdG8gcHV0IGluIG51bWVyaWNhbCBvcmRlciENCg0KcGFyYW1GcmFtZSA8LSB3aXRoKA0KICBsaXN0KA0KICAgIGJhc2FsMiA9IGMoNSwgMTAsIDE1KSwNCiAgICBjb25zdW1lcjIgPSBjKDIwLCA0MCwgNjApLA0KICAgIGxvZ0JvZHlTaXplID0gYygtMiwgLTEsIC0xLCAwKSwNCiAgICBwYXJhbWV0ZXJzID0gYygwLjAxLCAxMCwgMC41LCAwLjIsIDEwMCwgMC4xKQ0KICApLA0KICB7DQogICAgc2V0LnNlZWQoMzY4MDE4MCkNCiAgICBzZWVkc1ByZXAyIDwtIHJ1bmlmKDIgKiBsZW5ndGgoYmFzYWwyKSAqIGxlbmd0aChjb25zdW1lcjIpKSAqIDFFOA0KICAgIHdpdGgobGlzdCgNCiAgICAgIGIgPSByZXAoYmFzYWwyLCB0aW1lcyA9IGxlbmd0aChjb25zdW1lcjIpKSwNCiAgICAgIGMgPSByZXAoY29uc3VtZXIyLCBlYWNoID0gbGVuZ3RoKGJhc2FsMikpLA0KICAgICAgczEgPSBzZWVkc1ByZXAyWzE6KGxlbmd0aChiYXNhbDIpICogbGVuZ3RoKGNvbnN1bWVyMikpXSwNCiAgICAgIHMyID0gc2VlZHNQcmVwMlsNCiAgICAgICAgKGxlbmd0aChiYXNhbDIpICogbGVuZ3RoKGNvbnN1bWVyMikgKyAxKTooDQogICAgICAgICAgMiAqIGxlbmd0aChiYXNhbDIpICogbGVuZ3RoKGNvbnN1bWVyMikpDQogICAgICBdDQogICAgKSwgew0KICAgICAgcmJpbmQoDQogICAgICAgIHBhcmFtRnJhbWUsDQogICAgICAgIGRhdGEuZnJhbWUoDQogICAgICAgICAgQ29tYm5OdW0gPSB0ZXN0Um93c1RvQWRkLA0KICAgICAgICAgIEJhc2FscyA9IGJbdGVzdFJvd3NUb0FkZF0sDQogICAgICAgICAgQ29uc3VtZXJzID0gY1t0ZXN0Um93c1RvQWRkXSwNCiAgICAgICAgICBTZWVkUG9vbCA9IHMxW3Rlc3RSb3dzVG9BZGRdLA0KICAgICAgICAgIFNlZWRNYXQgPSBzMlt0ZXN0Um93c1RvQWRkXSwNCiAgICAgICAgICBTZWVkUnVucyA9ICIiLA0KICAgICAgICAgIFNlZWRSdW5zTnVtID0gMCwNCiAgICAgICAgICBFbmRTdGF0ZXMgPSBJKHJlcChsaXN0KCIiKSwgbGVuZ3RoKHRlc3RSb3dzVG9BZGQpKSksDQogICAgICAgICAgRW5kU3RhdGVzTnVtID0gMCwNCiAgICAgICAgICBFbmRTdGF0ZVNpemVzID0gSShyZXAobGlzdCgiIiksIGxlbmd0aCh0ZXN0Um93c1RvQWRkKSkpLA0KICAgICAgICAgIEVuZFN0YXRlU2l6ZXNOdW0gPSBOQSwNCiAgICAgICAgICBFbmRTdGF0ZUFzc2VtYmx5ID0gSShyZXAobGlzdCgiIiksIGxlbmd0aCh0ZXN0Um93c1RvQWRkKSkpLA0KICAgICAgICAgIEVuZFN0YXRlQWJ1bmRhbmNlID0gSShyZXAobGlzdCgiIiksIGxlbmd0aCh0ZXN0Um93c1RvQWRkKSkpLA0KICAgICAgICAgIERhdGFzZXQgPSAiVGVzdCIsDQogICAgICAgICAgRGF0YXNldElEID0gbWF4KHBhcmFtRnJhbWUkRGF0YXNldElEKSArIDEsDQogICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICAgICAgICkNCiAgICAgICkNCiAgICB9DQogICAgKQ0KICB9DQopDQoNCnRlc3RSb3dOdW1zIDwtICh0ZXN0Um93TnVtcyArIDEpOm5yb3cocGFyYW1GcmFtZSkNCnJlc3VsdHNMaXN0IDwtIGxpc3QoDQogIGxpc3QoDQogICAgIk5vIFJ1biIgPSAwLA0KICAgICJObyBTdGF0ZSIgPSAwLA0KICAgICIyLCA0LCA2LCAxMiwgMjkiID0gMSwNCiAgICAiMiwgNCwgNiwgMTMsIDI5IiA9IDENCiAgKSwNCiAgbGlzdCgNCiAgICAiTm8gUnVuIiA9IDAsDQogICAgIk5vIFN0YXRlIiA9IDAsDQogICAgIjgsIDEwLCAxMiwgMTQsIDE1LCAxNiwgMzksIDQzIiA9IDEsDQogICAgIjgsIDEyLCAxNCwgMTUsIDE2LCAzOCwgMzkiID0gMQ0KICApDQopDQpyZXN1bHRzU2l6ZSA8LSBsaXN0KA0KICBsaXN0KA0KICAgICIwIiA9IDAsDQogICAgIjUiID0gMg0KICApLA0KICBsaXN0KA0KICAgICIwIiA9IDAsDQogICAgIjgiID0gMSwNCiAgICAiNyIgPSAxDQogICkNCikNCnJlc3VsdHNBYnVuZCA8LSBsaXN0KA0KICBsaXN0KA0KICAgICJObyBSdW4iID0gIiIsDQogICAgIk5vIFN0YXRlIiA9ICIiLA0KICAgICIyLCA0LCA2LCAxMiwgMjkiID0gIjc0Mi44ODU1MzY3MTcxMiwgODAuNTc5MjMzMDcyNjI2LCAxNjIuMTI4Mzk5ODUwMjUzLCAyMC4yMDgyMTk4Njk5Mzg5LCAxOC44NTg5NDkwNTEwNDI5IiwNCiAgICAiMiwgNCwgNiwgMTMsIDI5IiA9ICI2NjguNjY0MTQzNTgxODM3LCAxMTkuMDI0MTQ2ODUxMDUyLCAxMjcuNjgwMjY5MzgzODY3LCAzMC42NTc5NjA4NjYwMzMsIDEzLjQ4NDQxOTQ3MDc5NDQiDQogICksDQogIGxpc3QoDQogICAgIk5vIFJ1biIgPSAiIiwNCiAgICAiTm8gU3RhdGUiID0gIiIsDQogICAgIjgsIDEwLCAxMiwgMTQsIDE1LCAxNiwgMzksIDQzIiA9ICIyMC43NjY1ODA3NjA2NDkxLCAzMi40NDYxMTY1MjYxNDU0LCA4MC40MDMzMzg3ODE4ODk1LCA4MTcuODc5NzIyMDMzMzU0LCAxMjEuMTM2NTcwNzgyODI4LCAxOC4wMzkwNjcxMDg4OTU3LCAxMi4zODM0NTYxMTc3MjcxLCAxOS45Njc0NzE4NTQzMTk2IiwNCiAgICAiOCwgMTIsIDE0LCAxNSwgMTYsIDM4LCAzOSIgPSAiODIuNTkyMDQ4NDkyODEyLCAxMzguMjY3Mzc5MTY2MDE0LCA5MzguMTU4NDM2Mzc5MTY2LCA1MS44NjEwOTYzNzQ1MDIxLCA1LjAzNTU2MjUxODM3NDkxLCAxNC4xMDE5MzQzMTQ1ODI1LCAyNS45MjMxMDYyNzExMjI4Ig0KICApDQopDQoNCmZvciAoaiBpbiBzZXFfYWxvbmcodGVzdFJvd051bXMpKSB7DQogIGkgPC0gdGVzdFJvd051bXNbal0NCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZXNbW2ldXSA8LSByZXN1bHRzTGlzdFtbal1dDQogIHBhcmFtRnJhbWUkRW5kU3RhdGVzTnVtW2ldIDwtIGxlbmd0aChyZXN1bHRzTGlzdFtbal1dKSAtIDIgIyAhIE5vIFN0YXRlLCBObyBSdW4NCiAgcGFyYW1GcmFtZSRFbmRTdGF0ZVNpemVzW1tpXV0gPC0gcmVzdWx0c1NpemVbW2pdXQ0KICBwYXJhbUZyYW1lJEVuZFN0YXRlU2l6ZXNOdW1baV0gPC0gbGVuZ3RoKHJlc3VsdHNTaXplW1tqXV0pIC0gMSAjICEgMA0KICBwYXJhbUZyYW1lJEVuZFN0YXRlQWJ1bmRhbmNlW1tpXV0gPC0gcmVzdWx0c0FidW5kW1tqXV0NCn0NCg0KYGBgDQoNCiMjIFBsb3QNCg0KYGBge3IgcGxvdDNEfQ0KIyBYLCBZLCBCYXNhbCBhbmQgQ29uc3VtZXIuDQojIFogPSBTaXplcyBvZiB0aGUgRW5kc3RhdGVzLg0KDQpwbG90U2NhbGluZ0RhdGEgPC0gZGF0YS5mcmFtZSgNCiAgQ29tYm5OdW0gPSByZXAocGFyYW1GcmFtZSRDb21ibk51bSwgcGFyYW1GcmFtZSRFbmRTdGF0ZXNOdW0pLA0KICBCYXNhbHMgPSByZXAocGFyYW1GcmFtZSRCYXNhbHMsIHBhcmFtRnJhbWUkRW5kU3RhdGVzTnVtKSwNCiAgQ29uc3VtZXJzID0gcmVwKHBhcmFtRnJhbWUkQ29uc3VtZXJzLCBwYXJhbUZyYW1lJEVuZFN0YXRlc051bSksDQogIERhdGFzZXQgPSByZXAocGFyYW1GcmFtZSREYXRhc2V0LCBwYXJhbUZyYW1lJEVuZFN0YXRlc051bSksDQogIERhdGFzZXRJRCA9IHJlcChwYXJhbUZyYW1lJERhdGFzZXRJRCwgcGFyYW1GcmFtZSRFbmRTdGF0ZXNOdW0pDQopDQoNCiMgQ29tbXVuaXRpZXMNCmNvbW1zIDwtIHVubGlzdChsYXBwbHkocGFyYW1GcmFtZSRFbmRTdGF0ZXMsIG5hbWVzKSkNCmZyZXFzIDwtIHVubGlzdChwYXJhbUZyYW1lJEVuZFN0YXRlcykNCmFzbWJsIDwtIHVubGlzdChwYXJhbUZyYW1lJEVuZFN0YXRlQXNzZW1ibHksIHJlY3Vyc2l2ZSA9IEZBTFNFKQ0KYXNtYmwgPC0gYXNtYmxbY29tbXMgIT0gIk5vIFJ1biIgJiBjb21tcyAhPSAiTm8gU3RhdGUiXQ0KZnJlcXMgPC0gZnJlcXNbY29tbXMgIT0gIk5vIFJ1biIgJiBjb21tcyAhPSAiTm8gU3RhdGUiXQ0KY29tbXMgPC0gY29tbXNbY29tbXMgIT0gIk5vIFJ1biIgJiBjb21tcyAhPSAiTm8gU3RhdGUiXQ0KDQphc21ibCA8LSBsYXBwbHkoYXNtYmwsIGZ1bmN0aW9uKGQpIHsNCiAgaWYgKGlzLm51bGwoZCkpIHJldHVybihOQSkNCiAgaWYgKCJSZXN1bHQuT3V0Y29tZSIgJWluJSBuYW1lcyhkKSkNCiAgICBkICU+JSBkcGx5cjo6ZmlsdGVyKFJlc3VsdC5PdXRjb21lICE9ICJUeXBlIDEgKEZhaWx1cmUpIiAmIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBSZXN1bHQuT3V0Y29tZSAhPSAiUHJlc2VudCIpDQogIGVsc2UNCiAgICBkJFJlc3VsdCAlPiUgZHBseXI6OmZpbHRlcihPdXRjb21lICE9ICJUeXBlIDEgKEZhaWx1cmUpIiAmIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT3V0Y29tZSAhPSAiUHJlc2VudCIpDQp9KQ0KDQpwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXRpZXMgPC0gY29tbXMNCnBsb3RTY2FsaW5nRGF0YSRDb21tdW5pdHlGcmVxIDwtIGZyZXFzDQpwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXR5U2VxIDwtIGFzbWJsDQoNCiMgQ29tbXVuaXR5IFNpemUNCnRlbXAgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXRpZXMsICcsJyksIGxlbmd0aCkpDQpwbG90U2NhbGluZ0RhdGEkQ29tbXVuaXR5U2l6ZSA8LSB0ZW1wDQoNCiMgRm9yIHVzYWdlIGJ5IHRoZSByZWFkZXIuDQoNCnBsb3RTY2FsaW5nIDwtIHBsb3RseTo6cGxvdF9seSgNCiAgcGxvdFNjYWxpbmdEYXRhLA0KICB4ID0gfkJhc2FscywNCiAgeSA9IH5Db25zdW1lcnMsDQogIHogPSB+Q29tbXVuaXR5U2l6ZSwNCiAgY29sb3IgPSB+RGF0YXNldCwNCiAgY29sb3JzID0gYygicmVkIiwgImJsdWUiLCAiYmxhY2siKQ0KKQ0KDQpwbG90U2NhbGluZyA8LSBwbG90bHk6OmFkZF9tYXJrZXJzKHBsb3RTY2FsaW5nKQ0KDQpwbG90U2NhbGluZyA8LSBwbG90bHk6OmxheW91dCgNCiAgcGxvdFNjYWxpbmcsDQogIHNjZW5lID0gbGlzdCgNCiAgICB4YXhpcyA9IGxpc3QodHlwZSA9ICJsb2ciKSwNCiAgICB5YXhpcyA9IGxpc3QodHlwZSA9ICJsb2ciKSwNCiAgICBjYW1lcmEgPSBsaXN0KA0KICAgICAgZXllID0gbGlzdCgNCiAgICAgICAgeCA9IC0xLjI1LCB5ID0gLTEuMjUsIHogPSAuMDUNCiAgICAgICkNCiAgICApDQogICkNCikNCg0KcGxvdFNjYWxpbmcNCmBgYA0KDQojIyBBYnVuZGFuY2VzDQoNCmBgYHtyIGxvYWRQb29sc01hdHN9DQojID4gcnVuaWYoMSkgKiAxRTgNCiMgWzFdIDgyNTk4Njc5DQpzZXQuc2VlZCg4MjU5ODY3OSkNCg0KbWF0cyA8LSBsaXN0KCkNCnBvb2xzYWxsIDwtIGxpc3QoKSAjIG5hbWUgcG9vbHMgdXNlZCBpbiBzYXZlIGRhdGE7IGJlIGNhcmVmdWwhDQoNCmZvciAoaSBpbiAxOmxlbmd0aChkaXJWaWtpbmcpKSB7DQogIHRlbXAgPC0gbG9hZChmaWxlLnBhdGgoDQogICAgZGlyVmlraW5nW2ldLCANCiAgICBwYXN0ZTAoIkxhd01vcnRvbjE5OTYtTnVtZXJpY2FsUG9vbENvbW11bml0eVNjYWxpbmctUG9vbE1hdHMiLCANCiAgICAgICAgICAgaWYgKGkgPiAxKSBpIGVsc2UgIiIsIA0KICAgICAgICAgICAiLlJEUyIpDQogICkpDQogIG1hdHNbW2ldXSA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wWzFdKSkNCiAgcG9vbHNhbGxbW2ldXSA8LSBldmFsKHBhcnNlKHRleHQgPSB0ZW1wWzJdKSkNCn0NCnBvb2xzIDwtIHBvb2xzYWxsDQoNCiMgQWRkIGluIHRoZSB0ZXN0IGRhdGFzZXRzLg0KcG9vbHNUZW1wIDwtIGxpc3QoKQ0KbWF0c1RlbXAgPC0gbGlzdCgpDQpmb3IgKHIgaW4gdGVzdFJvd051bXMpIHsNCiAgdGVzdFJvd1JvdyA8LSBwYXJhbUZyYW1lW3IsIF0NCiAgcG9vbHNUZW1wW1t0ZXN0Um93Um93JENvbWJuTnVtXV0gPC0gd2l0aCh0ZXN0Um93Um93LA0KICAgIFJNVFJDb2RlMjo6TGF3TW9ydG9uMTk5Nl9zcGVjaWVzKA0KICAgICAgQmFzYWwgPSBCYXNhbHMsDQogICAgICBDb25zdW1lciA9IENvbnN1bWVycywNCiAgICAgIFBhcmFtZXRlcnMgPSBjKDAuMDEsIDEwLCAwLjUsIDAuMiwgMTAwLCAwLjEpLA0KICAgICAgTG9nQm9keVNpemUgPSBjKC0yLCAtMSwgLTEsIDApLA0KICAgICAgc2VlZCA9IFNlZWRQb29sDQogICAgKQ0KICApDQogIG1hdHNUZW1wW1t0ZXN0Um93Um93JENvbWJuTnVtXV0gPC0gd2l0aCh0ZXN0Um93Um93LA0KICAgIFJNVFJDb2RlMjo6TGF3TW9ydG9uMTk5Nl9Db21tdW5pdHlNYXQoDQogICAgICBQb29sID0gcG9vbHNUZW1wW1tDb21ibk51bV1dLA0KICAgICAgUGFyYW1ldGVycyA9IGMoMC4wMSwgMTAsIDAuNSwgMC4yLCAxMDAsIDAuMSksDQogICAgICBzZWVkID0gU2VlZE1hdA0KICAgICkNCiAgKQ0KfQ0KcG9vbHNbW2kgKyAxXV0gPC0gcG9vbHNUZW1wDQptYXRzW1tpICsgMV1dIDwtIG1hdHNUZW1wDQoNCm9sZENhbmRpZGF0ZURhdGEgPC0gbG9hZChmaWxlLnBhdGgoZ2V0d2QoKSwgImNhbmRpZGF0ZURhdGFTb0Zhci5SZGF0YSIpKQ0Kb2xkQ2FuZGlkYXRlRGF0YSA8LSBldmFsKHBhcnNlKHRleHQgPSBvbGRDYW5kaWRhdGVEYXRhKSkNCmBgYA0KDQpgYGB7ciBjb21wdXRlQ2FuZGlkYXRlc30NCmNhbmRpZGF0ZURhdGEgPC0gcGxvdFNjYWxpbmdEYXRhICU+JSBkcGx5cjo6Z3JvdXBfYnkoDQogIENvbWJuTnVtLCBEYXRhc2V0DQopICU+JSBkcGx5cjo6bXV0YXRlKA0KICBPdGhlclN0ZWFkeVN0YXRlcyA9IGRwbHlyOjpuKCkgLSAxDQopICU+JSBkcGx5cjo6ZmlsdGVyKA0KICBPdGhlclN0ZWFkeVN0YXRlcyA+IDANCikNCmNhbmRpZGF0ZURhdGEgJT4lIGRwbHlyOjpzZWxlY3QoLUNvbW11bml0eVNlcSkNCmBgYA0KDQpgYGB7ciBsb2FkQWJ1bmRhbmNlc30NCiMgRmlyc3QsIGNoZWNrIGlmIGl0IGlzIGluIHRoZSBwYXJhbUZyYW1lLg0KIyBTZWNvbmQsIGNoZWNrIGlmIGl0IGlzIGluIHRoZSBzYXZlZCBkYXRhIGZyb20gdGhlIHByZXZpb3VzLg0KIyBPdGhlcndpc2UsIGlnbm9yZSBpdCwgd2UnbGwgZmlndXJlIG91dCB3aGF0IGl0IGlzIGFuZCB3aHkgaXQgaXMgbWlzc2luZyBsYXRlci4NCg0KY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZCA8LSAiIg0KDQpmb3IgKHIgaW4gMTpucm93KGNhbmRpZGF0ZURhdGEpKSB7DQogICMgSUQgMTo0IGFyZSB1c2VkIHRvIGlkZW50aWZ5IHBhcmFtRnJhbWUsIDUgdXNlZCB0byBpZGVudGlmeSBhYnVuZGFuY2UNCiAgSUQgPC0gY2FuZGlkYXRlRGF0YVtyLCAxOjZdDQogIHBhcmFtRnJhbWVSb3cgPC0gcGFyYW1GcmFtZSAlPiUgZHBseXI6OmZpbHRlcigNCiAgICBDb21ibk51bSA9PSBJRCRDb21ibk51bSwNCiAgICBCYXNhbHMgPT0gSUQkQmFzYWxzLA0KICAgIENvbnN1bWVycyA9PSBJRCRDb25zdW1lcnMsDQogICAgRGF0YXNldCA9PSBJRCREYXRhc2V0DQogICkNCiAgDQogIGlmIChpcy5saXN0KHBhcmFtRnJhbWVSb3ckRW5kU3RhdGVBYnVuZGFuY2VbWzFdXSkpIHsNCiAgICBlbnRyeSA8LSB3aGljaChJRCRDb21tdW5pdGllcyA9PSBuYW1lcyhwYXJhbUZyYW1lUm93JEVuZFN0YXRlQWJ1bmRhbmNlW1sxXV0pKQ0KICAgIGlmIChsZW5ndGgoZW50cnkpKSB7DQogICAgICBjYW5kaWRhdGVEYXRhJENvbW11bml0eUFidW5kW3JdIDwtIHBhcmFtRnJhbWVSb3ckRW5kU3RhdGVBYnVuZGFuY2VbWzFdXVtbZW50cnldXQ0KICAgICAgbmV4dCgpDQogICAgfQ0KICB9DQogIA0KICBpZiAoSUQkRGF0YXNldCA9PSAiMjAyMS0wNCIpIHsNCiAgICANCiAgICBvbGRDYW5kRGF0Um93IDwtIG9sZENhbmRpZGF0ZURhdGEgJT4lIGRwbHlyOjpmaWx0ZXIoDQogICAgICBDb21ibk51bSA9PSBJRCRDb21ibk51bSwNCiAgICAgIEJhc2FscyA9PSBJRCRCYXNhbHMsDQogICAgICBDb25zdW1lcnMgPT0gSUQkQ29uc3VtZXJzLA0KICAgICAgQ29tbXVuaXRpZXMgPT0gSUQkQ29tbXVuaXRpZXMNCiAgICApDQogICAgDQogICAgaWYgKG5yb3cob2xkQ2FuZERhdFJvdykgPiAwKSB7DQogICAgICBpZiAob2xkQ2FuZERhdFJvdyRDb21tdW5pdHlBYnVuZCAhPSAiIikgew0KICAgICAgICBjYW5kaWRhdGVEYXRhJENvbW11bml0eUFidW5kW3JdIDwtIG9sZENhbmREYXRSb3ckQ29tbXVuaXR5QWJ1bmQNCiAgICAgIH0NCiAgICB9DQogIH0NCn0NCmBgYA0KDQpgYGB7ciBjcmVhdGVBYnVuZH0NCmZvciAociBpbiAxOm5yb3coY2FuZGlkYXRlRGF0YSkpIHsNCiAgaWYgKCEoY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZFtyXSA9PSAiRmFpbHVyZSIgfA0KICAgICAgY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZFtyXSA9PSAiIikpIG5leHQNCg0KICAjIFJhbmRvbSBndWVzc2VzLCBzdGFydGluZyBmcm9tIHN0cnVjdHVyZWQuDQogIHRlbXAgPC0gd2l0aCgNCiAgICBjYW5kaWRhdGVEYXRhW3IsIF0sDQogICAgUk1UUkNvZGUyOjpGaW5kU3RlYWR5U3RhdGVGcm9tRXN0aW1hdGUoDQogICAgICBQb29sID0gcG9vbHNbW0RhdGFzZXRJRF1dW1tDb21ibk51bV1dLA0KICAgICAgSW50ZXJhY3Rpb25NYXRyaXggPSBtYXRzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSwNCiAgICAgIENvbW11bml0eSA9IENvbW11bml0aWVzLA0KICAgICAgUG9wdWxhdGlvbnMgPSBpZmVsc2UoDQogICAgICAgIHBvb2xzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSRUeXBlWw0KICAgICAgICAgIFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoQ29tbXVuaXRpZXMpDQogICAgICAgIF0gPT0gIkJhc2FsIiwNCiAgICAgICAgMTAwMCwgMTApDQogICAgKQ0KICApDQoNCiAgaWYgKGFueSh0ZW1wKSA8IDFFLTQpIHsNCiAgICB0ZW1wIDwtICJFc3RpbWF0ZUZhaWx1cmUiDQogIH0gZWxzZSB7DQogICAgdGVtcCA8LSB0b1N0cmluZyh0ZW1wKQ0KICB9DQogIGNhbmRpZGF0ZURhdGEkQ29tbXVuaXR5QWJ1bmRbcl0gPC0gdGVtcA0KfQ0KYGBgDQoNCmBgYHtyIGZpbHRlck5vQWJ1bmR9DQpjYW5kaWRhdGVEYXRhIDwtIGNhbmRpZGF0ZURhdGEgJT4lIGRwbHlyOjpmaWx0ZXIoQ29tbXVuaXR5QWJ1bmQgIT0gIiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29tbXVuaXR5QWJ1bmQgIT0gIkZhaWx1cmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvbW11bml0eUFidW5kICE9ICJFc3RpbWF0ZUZhaWx1cmUiKQ0KYGBgDQoNCmBgYHtyIGNvbXB1dGVQcm9kdWN0aXZpdHl9DQpjYW5kaWRhdGVEYXRhJENvbW11bml0eVByb2QgPC0gTkENCmZvciAociBpbiAxOm5yb3coY2FuZGlkYXRlRGF0YSkpIHsNCiAgY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlQcm9kW3JdIDwtIHdpdGgoDQogICAgY2FuZGlkYXRlRGF0YVtyLCBdLCANCiAgICBSTVRSQ29kZTI6OlByb2R1Y3Rpdml0eSgNCiAgICAgIFBvb2wgPSBwb29sc1tbRGF0YXNldElEXV1bW0NvbWJuTnVtXV0sIA0KICAgICAgSW50ZXJhY3Rpb25NYXRyaXggPSBtYXRzW1tEYXRhc2V0SURdXVtbQ29tYm5OdW1dXSwgDQogICAgICBDb21tdW5pdHkgPSBDb21tdW5pdGllcywgDQogICAgICBQb3B1bGF0aW9ucyA9IENvbW11bml0eUFidW5kDQogICAgKQ0KICApDQp9DQpgYGANCg0KIyMgU2ltcGxlIFBpcGUgUmVzdWx0cyB7LnRhYnNldH0NCiMjIyBTZXR1cA0KYGBge3IgcGlwZUZVTn0NCmZ1bmN0aW9uQ29tbXVuaXR5IDwtIGZ1bmN0aW9uKA0KICAgIHRpbWUsIA0KICAgIENvbmNlbnRyYXRpb24sIA0KICAgIHBhcmFtZXRlcnMsIA0KICAgIEdyaWRUaGlja25lc3MsIyA9IEdyaWQsDQogICAgUG9vbCwjID0gcHJlcHJvY2Vzc2VkJHJlZERpc1Bvb2wsIA0KICAgIE1hdCwjID0gcHJlcHJvY2Vzc2VkJHJlZENvbU1hdCwgDQogICAgQXJlYSA9IDEsIA0KICAgIERpZmZ1c2lvbiA9IDEsIA0KICAgIFZlcmJvc2UgPSBGQUxTRSwNCiAgICBFeHRpbmN0aW9uVGhyZXNob2xkID0gMCwgDQogICAgQ2lyY3VsYXIgPSBGQUxTRSwNCiAgICBub25Vbmlmb3JtRGlmZnVzaW9uID0gRkFMU0UNCikgew0KICAgIGlmKFZlcmJvc2UpIHByaW50KENvbmNlbnRyYXRpb24pDQogICAgDQogICAgQ29uY2VudHJhdGlvbltDb25jZW50cmF0aW9uIDwgRXh0aW5jdGlvblRocmVzaG9sZF0gPC0gMA0KICAgIA0KICAgICMgQ29sdW1ucyBhcmUgcGVyIHNwZWNpZXMgcGlwZXMsDQogICAgIyBSb3dzIGFyZSB0aGUgc2FtZSBwb2ludCBvbiBhIHBpcGUgYnV0IGZvciBlYWNoIHNwZWNpZXMuDQogICAgcGlwZU1hdHJpeCA8LSBtYXRyaXgoZGF0YSA9IENvbmNlbnRyYXRpb24sIG5jb2wgPSBucm93KFBvb2wpKQ0KICAgICMgTm90ZSB0aGF0IG1hdHJpeCBhdXRvbWF0aWNhbGx5IGZpbGxzIGRvd253YXJkIQ0KICAgIA0KICAgICMgSWRlYSB3aXRoIGNpcmN1bGFyOiBhdHRhY2ggZHVwbGljYXRlcyBvZiBmaXJzdCwgbGFzdCBldmFsdWF0aW9uIHBvaW50cy4NCiAgICAjIFRoZW4gZXZhbHVhdGUgYXMgbm9ybWFsLiBCZWZvcmUgZmluaXNoaW5nLCByZW1vdmUgdGhlIGR1cGxpY2F0ZSBlbnRyaWVzLg0KICAgICMgT3JkZXIgb2YgYWRkaXRpb25zIG1hdHRlcnMuDQogICAgaWYgKENpcmN1bGFyID4gMCkgew0KICAgICAgcGlwZU1hdHJpeCA8LSByYmluZChwaXBlTWF0cml4Wy1DaXJjdWxhcjotMSArIDEgK25yb3cocGlwZU1hdHJpeCksIF0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIHBpcGVNYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHBpcGVNYXRyaXhbMTpDaXJjdWxhciwgXSkNCiAgICAgIA0KICAgICAgaWYgKCJkeCIgJWluJSBuYW1lcyhHcmlkVGhpY2tuZXNzKSkgew0KICAgICAgICBHcmlkVGhpY2tuZXNzIDwtIEdyaWRUaGlja25lc3MkZHgNCiAgICAgIH0NCiAgICAgIEdyaWRUaGlja25lc3MgPC0gYyhHcmlkVGhpY2tuZXNzWy1DaXJjdWxhcjotMSArIDEgKyBsZW5ndGgoR3JpZFRoaWNrbmVzcyldLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEdyaWRUaGlja25lc3MsDQogICAgICAgICAgICAgICAgICAgICAgICAgR3JpZFRoaWNrbmVzc1sxOkNpcmN1bGFyXSkNCiAgICB9DQogICAgDQogICAgaWYoVmVyYm9zZSkgcHJpbnQocGlwZU1hdHJpeCkNCiAgICANCiAgICBwaXBlTGlzdCA8LSBsYXBwbHkoMTpuY29sKHBpcGVNYXRyaXgpLCANCiAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oaSwgbSkge21bLCBpXX0sIA0KICAgICAgICAgICAgICAgICAgICAgICBtID0gcGlwZU1hdHJpeCkNCiAgICANCiAgICAjIFRha2UgdGhlIHNwZWNpZXMgY29sdW1ucyB0byBiZSBpbiB0aGUgc2FtZSBvcmRlciBhcyB0aGUgcG9vbC4NCiAgICBwaXBlTGlzdFRyYW4gPC0gbGFwcGx5KA0KICAgICAgc2VxX2Fsb25nKHBpcGVMaXN0KSwgDQogICAgICBGVU4gPSBmdW5jdGlvbihpLCBDLCBBLCBELCBHLCBNKSB7DQogICAgICAgIFJlYWNUcmFuOjp0cmFuLjFEKA0KICAgICAgICAgIEMgPSBDW1tpXV0sIEEgPSBBLA0KICAgICAgICAgIEQgPSBEICogTSBeICgNCiAgICAgICAgICAgIGlmIChub25Vbmlmb3JtRGlmZnVzaW9uID09ICJwb3NpdGl2ZSIpIDAuNjINCiAgICAgICAgICAgIGVsc2UgaWYgKG5vblVuaWZvcm1EaWZmdXNpb24gPT0gIm5lZ2F0aXZlIikgLTAuNjINCiAgICAgICAgICAgIGVsc2UgMA0KICAgICAgICAgICksDQogICAgICAgICAgZHggPSBHDQogICAgICAgICkkZEMNCiAgICAgIH0sIA0KICAgICAgQyA9IHBpcGVMaXN0LCBBID0gQXJlYSwgRCA9IERpZmZ1c2lvbiwgRyA9IEdyaWRUaGlja25lc3MsIE0gPSBQb29sJFNpemUpDQogICAgDQogICAgIyBEaWZmdXNpb24NCiAgICBwaXBlTWF0cml4VHJhbiA8LSBtYXRyaXgoDQogICAgICB1bmxpc3QocGlwZUxpc3RUcmFuKSwNCiAgICAgIG5yb3cgPSBucm93KHBpcGVNYXRyaXgpLA0KICAgICAgbmNvbCA9IG5jb2wocGlwZU1hdHJpeCkNCiAgICApDQogICAgDQogICAgaWYoVmVyYm9zZSkgcHJpbnQocGlwZU1hdHJpeFRyYW4pDQogICAgDQogICAgIyBQb2ludC13aXNlIGNvbW11bml0eSBkeW5hbWljcw0KICAgIHBpcGVNYXRyaXhDb21tIDwtIHQoYXBwbHkoDQogICAgICBwaXBlTWF0cml4LCBNQVJHSU4gPSAxLCBGVU4gPSBmdW5jdGlvbihDLCB0LCBQLCBNKSB7DQogICAgICB1bmxpc3QoUk1UUkNvZGUyOjpHZW5lcmFsaXNlZExvdGthVm9sdGVycmEodCA9IHQsIHkgPSBDLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFybXMgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGEgPSBNLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSBQJFJlcHJvZHVjdGlvblJhdGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkpDQogICAgfSwgdCA9IHRpbWUsIFAgPSBQb29sLCBNID0gTWF0KSkNCiAgICANCiAgICBpZihWZXJib3NlKSBwcmludChwaXBlTWF0cml4Q29tbSkNCiAgICANCiAgICAjIE9yZGVyIG9mIGRlbGV0aW9ucyBkb2VzIG5vdCBtYXR0ZXIuDQogICAgaWYoQ2lyY3VsYXIpIHBpcGVNYXRyaXhUcmFuIDwtIHBpcGVNYXRyaXhUcmFuWw0KICAgICAgYygtMTotQ2lyY3VsYXIsIC0xOi1DaXJjdWxhciArIDEgKyBucm93KHBpcGVNYXRyaXhUcmFuKSksIF0gDQogICAgaWYoQ2lyY3VsYXIpIHBpcGVNYXRyaXhDb21tIDwtIHBpcGVNYXRyaXhDb21tWw0KICAgICAgYygtMTotQ2lyY3VsYXIsIC0xOi1DaXJjdWxhciArIDEgKyBucm93KHBpcGVNYXRyaXhDb21tKSksIF0gDQogICAgDQogICAgbGlzdChwaXBlTWF0cml4VHJhbiArIHBpcGVNYXRyaXhDb21tKQ0KICB9DQoNCnBpcGVGVU4gPC0gZnVuY3Rpb24oaSwgZGF0LCBwb29sLCBtYXQsIA0KICAgICAgICAgICAgICAgICAgICBHcmlkTCA9IDEsIEdyaWRDZWxscyA9IDEwMCwNCiAgICAgICAgICAgICAgICAgICAgVGltZSA9IHNlcSgwLCAxMDAwMCwgYnkgPSAyKSwNCiAgICAgICAgICAgICAgICAgICAgbm9uVW5pZm9ybURpZmZ1c2lvbiA9IEZBTFNFKSB7DQogIHRlbXAgPC0gZGF0W2ksIF0NCiAgDQogIHByZXByb2Nlc3NlZCA8LSBSTVRSQ29kZTI6OklzbGFuZFByZXByb2Nlc3MoDQogICAgUG9vbCA9IHBvb2wsIEludGVyYWN0aW9uTWF0cml4ID0gbWF0LA0KICAgIENvbW11bml0aWVzID0gYygNCiAgICAgIGxpc3QodGVtcCRDb21tdW5pdGllc1sxXSksDQogICAgICBsaXN0KHRlbXAkQ29tbXVuaXRpZXNbMl0pDQogICAgKSwNCiAgICBQb3B1bGF0aW9ucyA9IGMoDQogICAgICBsaXN0KHRlbXAkQ29tbXVuaXR5QWJ1bmRbMV0pLA0KICAgICAgbGlzdCh0ZW1wJENvbW11bml0eUFidW5kWzJdKQ0KICAgICksDQogICAgRGlzcGVyc2FsUG9vbCA9IDAsDQogICAgRGlzcGVyc2FsSXNsYW5kID0gbWF0cml4KDAsIG5yb3cgPSAyLCBuY29sID0gMiksDQogICAgVG9sZXJhbmNlID0gMCwgDQogICAgVmVyYm9zZSA9IEZBTFNFDQogICkNCiAgDQogICMgU2V0dXAgdGhlIGRlIHN5c3RlbS4NCiAgR3JpZCA8LSBSZWFjVHJhbjo6c2V0dXAuZ3JpZC4xRChMID0gR3JpZEwsIE4gPSBHcmlkQ2VsbHMpDQogIA0KICBpbml0aWFsQ29uZGl0aW9uIDwtIHdpdGgocHJlcHJvY2Vzc2VkLCByYmluZCgNCiAgICAjIENvbW11bml0eSAxDQogICAgbWF0cml4KGFidW5kYW5jZV9pbml0WzE6bGVuZ3RoKHJlZENvbSldLCBuY29sID0gbGVuZ3RoKHJlZENvbSkpLA0KICAgICMgU3BhY2UNCiAgICBtYXRyaXgoMCwgbnJvdyA9IEdyaWRDZWxscyAtIDIsIG5jb2wgPSBsZW5ndGgocmVkQ29tKSksDQogICAgIyBDb21tdW5pdHkgMg0KICAgIG1hdHJpeChhYnVuZGFuY2VfaW5pdFtsZW5ndGgocmVkQ29tKSArIDE6bGVuZ3RoKHJlZENvbSldLCBuY29sID0gbGVuZ3RoKHJlZENvbSkpDQogICkpDQogIA0KICAjIGh0dHBzOi8vd3d3LmpzdG9yLm9yZy9zdGFibGUvMzA3MTc4NiBmaWd1cmUgMQ0KICAjIHN1Z2dlc3RzIHNpemUtZGlzcGVyc2FsIHNob3VsZCBiZSAxMF4wLjYyDQogIGRlU29sdmU6Om9kZS4xRCgNCiAgICB5ID0gaW5pdGlhbENvbmRpdGlvbiwgDQogICAgdGltZXMgPSBUaW1lLA0KICAgIGZ1bmMgPSBmdW5jdGlvbkNvbW11bml0eSwNCiAgICBwYXJtcyA9IHByZXByb2Nlc3NlZCRwYXJhbWV0ZXJzLA0KICAgIEQgPSAwLjAwMSwNCiAgICBuc3BlYyA9IGxlbmd0aChwcmVwcm9jZXNzZWQkcmVkQ29tKSwNCiAgICBWZXJib3NlID0gRkFMU0UsDQogICAgR3JpZFRoaWNrbmVzcyA9IEdyaWQsDQogICAgUG9vbCA9IHByZXByb2Nlc3NlZCRyZWRQb29sLCANCiAgICBNYXQgPSBwcmVwcm9jZXNzZWQkcmVkQ29tTWF0LCANCiAgICBub25Vbmlmb3JtRGlmZnVzaW9uID0gbm9uVW5pZm9ybURpZmZ1c2lvbg0KICApDQp9DQpgYGANCg0KYGBge3IgcGlwZX0NCiMgRm9yIGVhY2ggZ3JvdXAtZGF0YXNldCwNCiMgRm9yIGVhY2ggcGFpciwNCiMgUnVuIFBpcGUgRHluYW1pY3MsDQojIFNhdmUgdGhlIHJlc3VsdCB3aXRoIGl0cyBwYWlyaW5nDQpjYW5kaWRhdGVEYXRhJFRvdGFsSUQgPC0gcGFzdGUoY2FuZGlkYXRlRGF0YSRDb21ibk51bSwgY2FuZGlkYXRlRGF0YSREYXRhc2V0SUQpDQoNCnBpcGVJbnRlcmFjdGlvbnMgPC0gbGlzdCgpDQpwaXBlSW50ZXJhY3Rpb25zRnVsbCA8LSBsaXN0KCkNCnBpcGVJbnRlcmFjdGlvbnNQIDwtIGxpc3QoKQ0KcGlwZUludGVyYWN0aW9uc1BGdWxsIDwtIGxpc3QoKQ0KcGlwZUludGVyYWN0aW9uc04gPC0gbGlzdCgpDQpwaXBlSW50ZXJhY3Rpb25zTkZ1bGwgPC0gbGlzdCgpDQpwaXBlSW50ZXJhY3Rpb25zVGVtcCA8LSBsaXN0KCkNCnBpcGVJbnRlcmFjdGlvbnNUZW1wRnVsbCA8LSBsaXN0KCkNCg0KZm9yIChncnAgaW4gdW5pcXVlKGNhbmRpZGF0ZURhdGEkVG90YWxJRCkpIHsNCiAgY2FuZGlkYXRlRGF0YVN1YnNldCA8LSBjYW5kaWRhdGVEYXRhICU+JSBkcGx5cjo6ZmlsdGVyKFRvdGFsSUQgPT0gZ3JwKQ0KICANCiAgaWYgKG5yb3coY2FuZGlkYXRlRGF0YVN1YnNldCkgPT0gMSkgbmV4dCgpDQogIA0KICBmb3IgKG5vblVuaWZvcm1EaWZmdXNpb24gaW4gYyhGQUxTRSwgInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpIHsNCiAgICBwYWlyaW5nUmVzdWx0cyA8LSBjb21ibigNCiAgICAgIG5yb3coY2FuZGlkYXRlRGF0YVN1YnNldCksIDIsIA0KICAgICAgcGlwZUZVTiwNCiAgICAgIGRhdCA9IGNhbmRpZGF0ZURhdGFTdWJzZXQsIA0KICAgICAgcG9vbCA9IHBvb2xzW1sNCiAgICAgICAgY2FuZGlkYXRlRGF0YVN1YnNldCREYXRhc2V0SURbMV0NCiAgICAgIF1dW1tjYW5kaWRhdGVEYXRhU3Vic2V0JENvbWJuTnVtWzFdXV0sDQogICAgICBtYXQgPSBtYXRzW1sNCiAgICAgICAgY2FuZGlkYXRlRGF0YVN1YnNldCREYXRhc2V0SURbMV0NCiAgICAgIF1dW1tjYW5kaWRhdGVEYXRhU3Vic2V0JENvbWJuTnVtWzFdXV0sDQogICAgICBzaW1wbGlmeSA9IEZBTFNFLA0KICAgICAgbm9uVW5pZm9ybURpZmZ1c2lvbiA9IG5vblVuaWZvcm1EaWZmdXNpb24NCiAgICApDQogICAgDQogICAgcGFpcmluZ1Jlc3VsdHNBYnVuZHMgPC0gbGFwcGx5KA0KICAgICAgcGFpcmluZ1Jlc3VsdHMsIGZ1bmN0aW9uKG1hdCwgY2VsbHMgPSAxMDApIHsNCiAgICAgICAgIyBGaW5hbCBvdXRjb21lcywgd2l0aG91dCB0aW1lLg0KICAgICAgICBtYXQgPC0gbWF0W25yb3cobWF0KSwgLTFdDQogICAgICAgIHJldFZhbCA8LSBsaXN0KCkNCiAgICAgICAgc3BlY2llcyA8LSBsZW5ndGgobWF0KSAvIGNlbGxzDQogICAgICAgICMgVGhlIHJlc3VsdHMgYXJlIGNvcGllcyBvZiB0aGUgcGlwZSBieSBzcGVjaWVzDQogICAgICAgICMgV2Ugd2FudCB0aGUgZW5kIHBvaW50cy4NCiAgICAgICAgZm9yIChpIGluIGMoMSwgY2VsbHMpKSB7DQogICAgICAgICAgcmV0VmFsW1t0b1N0cmluZyhpKV1dIDwtIG1hdFtpICsgY2VsbHMgKiAoKDE6c3BlY2llcykgLSAxKV0NCiAgICAgICAgfQ0KICAgICAgICByZXRWYWwNCiAgICAgIH0NCiAgICApDQogIA0KICAgIHBpcGVJbnRlcmFjdGlvbnNUZW1wRnVsbFtbDQogICAgICB0b1N0cmluZyhub25Vbmlmb3JtRGlmZnVzaW9uKV1dW1tncnBdXSA8LSBwYWlyaW5nUmVzdWx0cw0KICAgIHBpcGVJbnRlcmFjdGlvbnNUZW1wW1sNCiAgICAgIHRvU3RyaW5nKG5vblVuaWZvcm1EaWZmdXNpb24pXV1bW2dycF1dIDwtIHBhaXJpbmdSZXN1bHRzQWJ1bmRzDQogIH0NCn0NCg0KcGlwZUludGVyYWN0aW9uc0Z1bGwgPC0gcGlwZUludGVyYWN0aW9uc1RlbXBGdWxsW1siRkFMU0UiXV0NCnBpcGVJbnRlcmFjdGlvbnMgPC0gcGlwZUludGVyYWN0aW9uc1RlbXBbWyJGQUxTRSJdXQ0KcGlwZUludGVyYWN0aW9uc1BGdWxsIDwtIHBpcGVJbnRlcmFjdGlvbnNUZW1wRnVsbFtbInBvc2l0aXZlIl1dDQpwaXBlSW50ZXJhY3Rpb25zUCA8LSBwaXBlSW50ZXJhY3Rpb25zVGVtcFtbInBvc2l0aXZlIl1dDQpwaXBlSW50ZXJhY3Rpb25zTkZ1bGwgPC0gcGlwZUludGVyYWN0aW9uc1RlbXBGdWxsW1sibmVnYXRpdmUiXV0NCnBpcGVJbnRlcmFjdGlvbnNOIDwtIHBpcGVJbnRlcmFjdGlvbnNUZW1wW1sibmVnYXRpdmUiXV0NCmBgYA0KDQojIyMgUmVzdWx0cyBTaXplLU5lZ2F0aXZlDQpgYGB7ciBjb21wYXJlUGlwZUR5bmFtaWNzTn0NCiMgRm9ybWF0IG9mIHRhYmxlIHNob3VsZCBiZToNCiMgSUQsIENvbW11bml0eSAxLCBDb21tdW5pdHkgMiwgT3V0Y29tZXMgMS0yLCBPdXRjb21lcyAxLTAtMg0KIyBGb3Igb3V0Y29tZXMsIHNwZWNpZXMgcHJlc2VuY2Ugd2lsbCBiZSB1c2VkLg0KDQpjb21tdW5pdGllcyA8LSBOVUxMDQp0b3RhbENvbW11bml0aWVzIDwtIE5VTEwNCmZvciAoZ3JwIGluIHVuaXF1ZShjYW5kaWRhdGVEYXRhJFRvdGFsSUQpKSB7DQogIGNhbmRpZGF0ZURhdGFTdWJzZXQgPC0gY2FuZGlkYXRlRGF0YSAlPiUgZHBseXI6OmZpbHRlcihUb3RhbElEID09IGdycCkNCiAgDQogIGlmIChucm93KGNhbmRpZGF0ZURhdGFTdWJzZXQpID4gMSkgew0KICAgIG5ld0NvbW11bml0aWVzIDwtIGNvbWJuKA0KICAgICAgY2FuZGlkYXRlRGF0YVN1YnNldCRDb21tdW5pdGllcywgMiwgDQogICAgKQ0KICAgIGNvbW11bml0aWVzIDwtIGMoY29tbXVuaXRpZXMsIG5ld0NvbW11bml0aWVzKQ0KICAgIHRvdGFsQ29tbXVuaXRpZXMgPC0gYyggIyBMYWJlbGxpbmcgaXMgd3JvbmcgaGVyZS4gTmVlZCB0QyBmcm9tIHBhaXJzIG9mIG5DLg0KICAgICAgdG90YWxDb21tdW5pdGllcywNCiAgICAgIGxpc3QoYXBwbHkobmV3Q29tbXVuaXRpZXMsIDIsIGZ1bmN0aW9uKGNvbXMpIHsNCiAgICAgICAgdG9TdHJpbmcoc29ydCh1bmlxdWUoUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChjb21zKSkpKQ0KICAgICAgfSkpDQogICAgKQ0KICB9DQp9DQoNCm1pblRocmVzaCA8LSBsYXBwbHkoY2FuZGlkYXRlRGF0YSRDb21tdW5pdHlBYnVuZCwgZnVuY3Rpb24oeCkgbWluKFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoeCkpICogMC4xKSAlPiUgdW5saXN0ICU+JSBtaW4NCg0KcGlwZUludGVyYWN0aW9uc05XaGljaCA8LSB1bmxpc3QobGFwcGx5KA0KICBzZXFfYWxvbmcocGlwZUludGVyYWN0aW9uc04pLCBmdW5jdGlvbihpLCBieUlELCBjb21tdW5pdGllcykgew0KICAgIGxhcHBseShieUlEW1tpXV0sIGZ1bmN0aW9uKHN5c3RlbSwgY29tbXMpIHsNCiAgICAgIGxhcHBseShzeXN0ZW0sIGZ1bmN0aW9uKHBpcGVFbmQsIGNvbW1zKSB7DQogICAgICAgIHRvU3RyaW5nKFJNVFJDb2RlMjo6Q3N2Um93U3BsaXQoY29tbXMpWw0KICAgICAgICAgIHdoaWNoKHBpcGVFbmQgPiBtaW5UaHJlc2gpDQogICAgICAgIF0pDQogICAgICB9LCBjb21tcyA9IGNvbW1zKQ0KICAgIH0sIGNvbW1zID0gY29tbXVuaXRpZXNbW2ldXSkNCiAgfSwgYnlJRCA9IHBpcGVJbnRlcmFjdGlvbnNOLCBjb21tdW5pdGllcyA9IHRvdGFsQ29tbXVuaXRpZXMNCikpDQoNCnBpcGVJbnRlcmFjdGlvbk5SZXN1bHRzIDwtIGRhdGEuZnJhbWUoDQogIERhdGFzZXRJRCA9IHJlcChuYW1lcyhwaXBlSW50ZXJhY3Rpb25zTiksIA0KICAgICAgICAgICAgICAgICAgdW5saXN0KGxhcHBseShwaXBlSW50ZXJhY3Rpb25zTiwgbGVuZ3RoKSkpLA0KICBDb21tdW5pdHkxID0gY29tbXVuaXRpZXNbc2VxKGZyb20gPSAxLCB0byA9IGxlbmd0aChjb21tdW5pdGllcyksIGJ5ID0gMildLA0KICBDb21tdW5pdHkyID0gY29tbXVuaXRpZXNbc2VxKGZyb20gPSAyLCB0byA9IGxlbmd0aChjb21tdW5pdGllcyksIGJ5ID0gMildLA0KICBPdXRjb21lX1BpcGUxID0gcGlwZUludGVyYWN0aW9uc05XaGljaFsNCiAgICBzZXEoZnJvbSA9IDEsIHRvID0gbGVuZ3RoKHBpcGVJbnRlcmFjdGlvbnNOV2hpY2gpLCBieSA9IDIpXSwNCiAgT3V0Y29tZV9QaXBlMiA9IHBpcGVJbnRlcmFjdGlvbnNOV2hpY2hbDQogICAgc2VxKGZyb20gPSAxLCB0byA9IGxlbmd0aChwaXBlSW50ZXJhY3Rpb25zTldoaWNoKSwgYnkgPSAyKV0NCikNCg0KcGlwZUludGVyYWN0aW9uTlJlc3VsdHMNCmBgYA0KDQpgYGB7ciBtYXRjaGVzTn0NCnBpcGVJbnRlcmFjdGlvbk5SZXN1bHRzICU+JSBkcGx5cjo6bXV0YXRlKA0KICBDMUludmFkZWQgPSBDb21tdW5pdHkxICE9IE91dGNvbWVfUGlwZTEsDQogIEMySW52YWRlZCA9IENvbW11bml0eTIgIT0gT3V0Y29tZV9QaXBlMiwNCiAgU3RhbGVtYXRlID0gIUMxSW52YWRlZCAmICFDMkludmFkZWQsDQogIEh5YnJpZCA9IEMxSW52YWRlZCAmIEMySW52YWRlZCwNCikgJT4lIGRwbHlyOjpzZWxlY3QoLWRwbHlyOjpzdGFydHNfd2l0aCgiT3V0Y29tZSIpKQ0KYGBgDQoNCiMjIyBSZXN1bHRzIFNpemUtTmV1dHJhbA0KYGBge3IgY29tcGFyZVBpcGVEeW5hbWljc30NCnBpcGVJbnRlcmFjdGlvbnNXaGljaCA8LSB1bmxpc3QobGFwcGx5KA0KICBzZXFfYWxvbmcocGlwZUludGVyYWN0aW9ucyksIGZ1bmN0aW9uKGksIGJ5SUQsIGNvbW11bml0aWVzKSB7DQogICAgbGFwcGx5KGJ5SURbW2ldXSwgZnVuY3Rpb24oc3lzdGVtLCBjb21tcykgew0KICAgICAgbGFwcGx5KHN5c3RlbSwgZnVuY3Rpb24ocGlwZUVuZCwgY29tbXMpIHsNCiAgICAgICAgdG9TdHJpbmcoUk1UUkNvZGUyOjpDc3ZSb3dTcGxpdChjb21tcylbDQogICAgICAgICAgd2hpY2gocGlwZUVuZCA+IG1pblRocmVzaCkNCiAgICAgICAgXSkNCiAgICAgIH0sIGNvbW1zID0gY29tbXMpDQogICAgfSwgY29tbXMgPSBjb21tdW5pdGllc1tbaV1dKQ0KICB9LCBieUlEID0gcGlwZUludGVyYWN0aW9ucywgY29tbXVuaXRpZXMgPSB0b3RhbENvbW11bml0aWVzDQopKQ0KDQpwaXBlSW50ZXJhY3Rpb25SZXN1bHRzIDwtIGRhdGEuZnJhbWUoDQogIERhdGFzZXRJRCA9IHJlcChuYW1lcyhwaXBlSW50ZXJhY3Rpb25zKSwgDQogICAgICAgICAgICAgICAgICB1bmxpc3QobGFwcGx5KHBpcGVJbnRlcmFjdGlvbnMsIGxlbmd0aCkpKSwNCiAgQ29tbXVuaXR5MSA9IGNvbW11bml0aWVzW3NlcShmcm9tID0gMSwgdG8gPSBsZW5ndGgoY29tbXVuaXRpZXMpLCBieSA9IDIpXSwNCiAgQ29tbXVuaXR5MiA9IGNvbW11bml0aWVzW3NlcShmcm9tID0gMiwgdG8gPSBsZW5ndGgoY29tbXVuaXRpZXMpLCBieSA9IDIpXSwNCiAgT3V0Y29tZV9QaXBlMSA9IHBpcGVJbnRlcmFjdGlvbnNXaGljaFsNCiAgICBzZXEoZnJvbSA9IDEsIHRvID0gbGVuZ3RoKHBpcGVJbnRlcmFjdGlvbnNXaGljaCksIGJ5ID0gMildLA0KICBPdXRjb21lX1BpcGUyID0gcGlwZUludGVyYWN0aW9uc1doaWNoWw0KICAgIHNlcShmcm9tID0gMSwgdG8gPSBsZW5ndGgocGlwZUludGVyYWN0aW9uc1doaWNoKSwgYnkgPSAyKV0NCikNCg0KcGlwZUludGVyYWN0aW9uUmVzdWx0cw0KYGBgDQoNCmBgYHtyIG1hdGNoZXN9DQpwaXBlSW50ZXJhY3Rpb25SZXN1bHRzICU+JSBkcGx5cjo6bXV0YXRlKA0KICBDMUludmFkZWQgPSBDb21tdW5pdHkxICE9IE91dGNvbWVfUGlwZTEsDQogIEMySW52YWRlZCA9IENvbW11bml0eTIgIT0gT3V0Y29tZV9QaXBlMiwNCiAgU3RhbGVtYXRlID0gIUMxSW52YWRlZCAmICFDMkludmFkZWQsDQogIEh5YnJpZCA9IEMxSW52YWRlZCAmIEMySW52YWRlZCwNCikgJT4lIGRwbHlyOjpzZWxlY3QoLWRwbHlyOjpzdGFydHNfd2l0aCgiT3V0Y29tZSIpKQ0KYGBgDQojIyMgUmVzdWx0cyBTaXplLVBvc2l0aXZlDQpgYGB7ciBjb21wYXJlUGlwZUR5bmFtaWNzUH0NCnBpcGVJbnRlcmFjdGlvbnNQV2hpY2ggPC0gdW5saXN0KGxhcHBseSgNCiAgc2VxX2Fsb25nKHBpcGVJbnRlcmFjdGlvbnNQKSwgZnVuY3Rpb24oaSwgYnlJRCwgY29tbXVuaXRpZXMpIHsNCiAgICBsYXBwbHkoYnlJRFtbaV1dLCBmdW5jdGlvbihzeXN0ZW0sIGNvbW1zKSB7DQogICAgICBsYXBwbHkoc3lzdGVtLCBmdW5jdGlvbihwaXBlRW5kLCBjb21tcykgew0KICAgICAgICB0b1N0cmluZyhSTVRSQ29kZTI6OkNzdlJvd1NwbGl0KGNvbW1zKVsNCiAgICAgICAgICB3aGljaChwaXBlRW5kID4gbWluVGhyZXNoKQ0KICAgICAgICBdKQ0KICAgICAgfSwgY29tbXMgPSBjb21tcykNCiAgICB9LCBjb21tcyA9IGNvbW11bml0aWVzW1tpXV0pDQogIH0sIGJ5SUQgPSBwaXBlSW50ZXJhY3Rpb25zUCwgY29tbXVuaXRpZXMgPSB0b3RhbENvbW11bml0aWVzDQopKQ0KDQpwaXBlSW50ZXJhY3Rpb25QUmVzdWx0cyA8LSBkYXRhLmZyYW1lKA0KICBEYXRhc2V0SUQgPSByZXAobmFtZXMocGlwZUludGVyYWN0aW9uc1ApLCANCiAgICAgICAgICAgICAgICAgIHVubGlzdChsYXBwbHkocGlwZUludGVyYWN0aW9uc1AsIGxlbmd0aCkpKSwNCiAgQ29tbXVuaXR5MSA9IGNvbW11bml0aWVzW3NlcShmcm9tID0gMSwgdG8gPSBsZW5ndGgoY29tbXVuaXRpZXMpLCBieSA9IDIpXSwNCiAgQ29tbXVuaXR5MiA9IGNvbW11bml0aWVzW3NlcShmcm9tID0gMiwgdG8gPSBsZW5ndGgoY29tbXVuaXRpZXMpLCBieSA9IDIpXSwNCiAgT3V0Y29tZV9QaXBlMSA9IHBpcGVJbnRlcmFjdGlvbnNQV2hpY2hbDQogICAgc2VxKGZyb20gPSAxLCB0byA9IGxlbmd0aChwaXBlSW50ZXJhY3Rpb25zUFdoaWNoKSwgYnkgPSAyKV0sDQogIE91dGNvbWVfUGlwZTIgPSBwaXBlSW50ZXJhY3Rpb25zUFdoaWNoWw0KICAgIHNlcShmcm9tID0gMSwgdG8gPSBsZW5ndGgocGlwZUludGVyYWN0aW9uc1BXaGljaCksIGJ5ID0gMildDQopDQoNCnBpcGVJbnRlcmFjdGlvblBSZXN1bHRzDQpgYGANCg0KYGBge3IgbWF0Y2hlc1B9DQpwaXBlSW50ZXJhY3Rpb25QUmVzdWx0cyAlPiUgZHBseXI6Om11dGF0ZSgNCiAgQzFJbnZhZGVkID0gQ29tbXVuaXR5MSAhPSBPdXRjb21lX1BpcGUxLA0KICBDMkludmFkZWQgPSBDb21tdW5pdHkyICE9IE91dGNvbWVfUGlwZTIsDQogIFN0YWxlbWF0ZSA9ICFDMUludmFkZWQgJiAhQzJJbnZhZGVkLA0KICBIeWJyaWQgPSBDMUludmFkZWQgJiBDMkludmFkZWQsDQopICU+JSBkcGx5cjo6c2VsZWN0KC1kcGx5cjo6c3RhcnRzX3dpdGgoIk91dGNvbWUiKSkNCmBgYA0KDQojIFNhdmUgd29ya3NwYWNlDQpgYGB7ciBzYXZlfQ0Kc2F2ZSgNCiAgY2FuZGlkYXRlRGF0YSwNCiAgcGlwZUludGVyYWN0aW9ucywNCiAgcGlwZUludGVyYWN0aW9uc1doaWNoLA0KICBwaXBlSW50ZXJhY3Rpb25zUCwNCiAgcGlwZUludGVyYWN0aW9uc1BXaGljaCwNCiAgcGlwZUludGVyYWN0aW9uc04sDQogIHBpcGVJbnRlcmFjdGlvbnNOV2hpY2gsDQogIG1hdHMsDQogIHBhcmFtRnJhbWUsDQogIHBsb3RTY2FsaW5nRGF0YSwNCiAgcG9vbHMsDQogIGZpbGUgPSBwYXN0ZTAoIkxNMTk5Ni1OdW1Qb29sQ29tLVFEYXREaWYiLA0KICAgICAgICAgICAgICAgICItMjAyMS0wNy5SRGF0YSIpDQopDQoNCnNhdmUoDQogIHBpcGVJbnRlcmFjdGlvbnNORnVsbCwNCiAgcGlwZUludGVyYWN0aW9uc0Z1bGwsDQogIHBpcGVJbnRlcmFjdGlvbnNQRnVsbCwNCiAgZmlsZSA9IHBhc3RlMCgiTE0xOTk2LU51bVBvb2xDb20tUURhdERpZiIsDQogICAgICAgICAgICAgICAgIkZVTEwtMjAyMS0wNy5SRGF0YSIpDQopDQpgYGANCg==